Mercurial > hg > ucis.core
annotate Net/HTTP.cs @ 84:146a8d224d86
Added ArrayUtil.Remove, fixed some exceptons
author | Ivo Smits <Ivo@UCIS.nl> |
---|---|
date | Thu, 05 Jun 2014 00:21:02 +0200 |
parents | 50d4aed66c67 |
children | ebdff34b9e4f |
rev | line source |
---|---|
0 | 1 ???using System; |
2 using System.Collections.Generic; | |
3 using System.IO; | |
3 | 4 using System.Net; |
10 | 5 using System.Net.Security; |
3 | 6 using System.Net.Sockets; |
10 | 7 using System.Security.Cryptography.X509Certificates; |
0 | 8 using System.Text; |
6 | 9 using UCIS.Net; |
3 | 10 using UCIS.Util; |
11 using HTTPHeader = System.Collections.Generic.KeyValuePair<string, string>; | |
0 | 12 |
13 namespace UCIS.Net.HTTP { | |
3 | 14 public class HTTPServer : TCPServer.IModule, IDisposable { |
15 public IHTTPContentProvider ContentProvider { get; set; } | |
16 public Boolean ServeFlashPolicyFile { get; set; } | |
10 | 17 public X509Certificate SSLCertificate { get; set; } |
3 | 18 private Socket Listener = null; |
0 | 19 |
3 | 20 public HTTPServer() { } |
0 | 21 |
3 | 22 public void Listen(int port) { |
23 Listen(new IPEndPoint(IPAddress.Any, port)); | |
0 | 24 } |
25 | |
3 | 26 public void Listen(EndPoint localep) { |
27 if (Listener != null) throw new InvalidOperationException("A listener exists"); | |
28 Listener = new Socket(localep.AddressFamily, SocketType.Stream, ProtocolType.Tcp); | |
29 Listener.Bind(localep); | |
30 Listener.Listen(5); | |
31 Listener.BeginAccept(AcceptCallback, null); | |
0 | 32 } |
33 | |
3 | 34 private void AcceptCallback(IAsyncResult ar) { |
35 try { | |
36 Socket socket = Listener.EndAccept(ar); | |
10 | 37 if (SSLCertificate != null) { |
38 SslStream ssl = new SslStream(new NetworkStream(socket, true)); | |
39 ssl.BeginAuthenticateAsServer(SSLCertificate, SslAuthenticationCallback, new Object[] { socket, ssl }); | |
40 } else { | |
41 new HTTPContext(this, socket); | |
42 } | |
3 | 43 } catch (Exception) { } |
44 try { | |
45 Listener.BeginAccept(AcceptCallback, null); | |
46 } catch (Exception) { } | |
0 | 47 } |
48 | |
10 | 49 private void SslAuthenticationCallback(IAsyncResult ar) { |
50 Object[] args = (Object[])ar.AsyncState; | |
51 Socket socket = (Socket)args[0]; | |
52 SslStream ssl = (SslStream)args[1]; | |
53 try { | |
54 ssl.EndAuthenticateAsServer(ar); | |
55 new HTTPContext(this, ssl, socket); | |
56 } catch (Exception) { } | |
57 } | |
58 | |
3 | 59 public void Dispose() { |
60 if (Listener != null) Listener.Close(); | |
0 | 61 } |
62 | |
45
8df7f4dc5615
HTTP Server: enable KeepAlive option on TCP sockets
Ivo Smits <Ivo@UCIS.nl>
parents:
16
diff
changeset
|
63 public void HandleClient(Socket client) { |
8df7f4dc5615
HTTP Server: enable KeepAlive option on TCP sockets
Ivo Smits <Ivo@UCIS.nl>
parents:
16
diff
changeset
|
64 new HTTPContext(this, client); |
8df7f4dc5615
HTTP Server: enable KeepAlive option on TCP sockets
Ivo Smits <Ivo@UCIS.nl>
parents:
16
diff
changeset
|
65 } |
8df7f4dc5615
HTTP Server: enable KeepAlive option on TCP sockets
Ivo Smits <Ivo@UCIS.nl>
parents:
16
diff
changeset
|
66 |
3 | 67 bool TCPServer.IModule.Accept(TCPStream stream) { |
68 new HTTPContext(this, stream); | |
69 return false; | |
0 | 70 } |
71 } | |
72 | |
75 | 73 public enum HTTPResponseStreamMode { |
74 None = -1, | |
75 Direct = 0, | |
76 Buffered = 1, | |
77 Chunked = 2, | |
78 Hybrid = 3, | |
79 } | |
80 | |
0 | 81 public class HTTPContext { |
3 | 82 public HTTPServer Server { get; private set; } |
83 public EndPoint LocalEndPoint { get; private set; } | |
84 public EndPoint RemoteEndPoint { get; private set; } | |
85 | |
86 public String RequestMethod { get; private set; } | |
87 public String RequestPath { get; private set; } | |
88 public String RequestQuery { get; private set; } | |
89 public int HTTPVersion { get; set; } | |
90 | |
91 public Socket Socket { get; private set; } | |
92 public Boolean SuppressStandardHeaders { get; set; } | |
6 | 93 public TCPStream TCPStream { get; private set; } |
3 | 94 |
95 private StreamWriter Writer; | |
6 | 96 private PrebufferingStream Reader; |
3 | 97 private List<HTTPHeader> RequestHeaders; |
98 private HTTPConnectionState State = HTTPConnectionState.Starting; | |
75 | 99 private KeyValuePair<String, String>[] QueryParameters = null, PostParameters = null; |
100 private HTTPOutputStream ResponseStream = null; | |
101 private HTTPInputStream RequestStream = null; | |
10 | 102 |
3 | 103 private enum HTTPConnectionState { |
104 Starting = 0, | |
105 ReceivingRequest = 1, | |
106 ProcessingRequest = 2, | |
107 SendingHeaders = 3, | |
108 SendingContent = 4, | |
75 | 109 Completed = 5, |
110 Closed = 6, | |
111 } | |
112 | |
113 private class HTTPOutputStream : Stream { | |
114 public HTTPResponseStreamMode Mode { get; private set; } | |
115 public HTTPContext Context { get; private set; } | |
116 private Stream OutputStream = null; | |
117 private MemoryStream Buffer = null; | |
118 private long BytesWritten = 0; | |
119 private long MaxLength; | |
120 | |
121 public HTTPOutputStream(HTTPContext context, HTTPResponseStreamMode mode) : this(context, mode, -1) { } | |
122 public HTTPOutputStream(HTTPContext context, HTTPResponseStreamMode mode, long length) { | |
123 this.Context = context; | |
124 this.Mode = mode; | |
125 this.MaxLength = length; | |
126 switch (Mode) { | |
127 case HTTPResponseStreamMode.Direct: | |
128 if (MaxLength != -1) Context.SendHeader("Content-Length", MaxLength.ToString()); | |
129 OutputStream = Context.BeginResponseData(); | |
130 break; | |
131 case HTTPResponseStreamMode.Chunked: | |
132 Context.SendHeader("Transfer-Encoding", "chunked"); | |
133 OutputStream = Context.BeginResponseData(); | |
134 break; | |
135 case HTTPResponseStreamMode.Buffered: | |
136 case HTTPResponseStreamMode.Hybrid: | |
137 if (Context.State != HTTPConnectionState.ProcessingRequest && Context.State != HTTPConnectionState.SendingHeaders) throw new InvalidOperationException("The response stream can not be created in the current state"); | |
138 break; | |
139 default: throw new InvalidOperationException("Response stream mode is not supported"); | |
140 } | |
141 } | |
142 | |
143 private void WriteBuffered(byte[] buffer, int offset, int count) { | |
144 if (Buffer == null) Buffer = new MemoryStream(); | |
145 Buffer.Write(buffer, offset, count); | |
146 } | |
147 private void WriteChunked(byte[] buffer, int offset, int count) { | |
148 Byte[] lb = Encoding.ASCII.GetBytes(count.ToString("X") + "\r\n"); | |
149 OutputStream.Write(lb, 0, lb.Length); | |
150 if (count != 0) OutputStream.Write(buffer, offset, count); | |
151 OutputStream.Write(new Byte[] { (Byte)'\r', (Byte)'\n' }, 0, 2); | |
152 } | |
153 private void HybridSwitchToChunked() { | |
154 MemoryStream oldbuffer = Buffer; | |
155 Buffer = null; | |
156 Context.SendHeader("Transfer-Encoding", "chunked"); | |
157 OutputStream = Context.BeginResponseData(); | |
158 Mode = HTTPResponseStreamMode.Chunked; | |
84
146a8d224d86
Added ArrayUtil.Remove, fixed some exceptons
Ivo Smits <Ivo@UCIS.nl>
parents:
75
diff
changeset
|
159 if (oldbuffer != null) oldbuffer.WriteTo(this); |
75 | 160 } |
161 | |
162 public override void Write(byte[] buffer, int offset, int count) { | |
163 if (offset < 0 || count < 0 || offset + count > buffer.Length) throw new ArgumentOutOfRangeException("buffer", "Offset and count arguments exceed the buffer dimensions"); | |
164 switch (Mode) { | |
165 case HTTPResponseStreamMode.Direct: | |
166 if (MaxLength != -1 && BytesWritten + count > MaxLength) throw new InvalidOperationException("The write operation exceeds the transfer length"); | |
167 OutputStream.Write(buffer, offset, count); | |
168 BytesWritten += count; | |
169 break; | |
170 case HTTPResponseStreamMode.Buffered: | |
171 WriteBuffered(buffer, offset, count); | |
172 break; | |
173 case HTTPResponseStreamMode.Chunked: | |
174 if (count != 0) WriteChunked(buffer, offset, count); | |
175 BytesWritten += count; | |
176 break; | |
177 case HTTPResponseStreamMode.Hybrid: | |
178 if (count > 1024 || (Buffer != null && Buffer.Length + count > 1024)) { | |
179 HybridSwitchToChunked(); | |
180 if (count != 0) WriteChunked(buffer, offset, count); | |
181 } else { | |
182 WriteBuffered(buffer, offset, count); | |
183 } | |
184 break; | |
185 } | |
186 } | |
187 class CompletedAsyncResult : AsyncResultBase { | |
188 public CompletedAsyncResult(AsyncCallback callback, Object state) : base(callback, state) { | |
189 SetCompleted(true, null); | |
190 } | |
191 } | |
192 public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback callback, object state) { | |
193 if (offset < 0 || count < 0 || offset + count > buffer.Length) throw new ArgumentOutOfRangeException("buffer", "Offset and count arguments exceed the buffer dimensions"); | |
194 switch (Mode) { | |
195 case HTTPResponseStreamMode.Direct: | |
196 if (MaxLength != -1 && BytesWritten + count > MaxLength) throw new InvalidOperationException("The write operation exceeds the transfer length"); | |
197 BytesWritten += count; | |
198 return OutputStream.BeginWrite(buffer, offset, count, callback, state); | |
199 case HTTPResponseStreamMode.Buffered: | |
200 case HTTPResponseStreamMode.Chunked: | |
201 case HTTPResponseStreamMode.Hybrid: | |
202 Write(buffer, offset, count); | |
203 return new CompletedAsyncResult(callback, state); | |
204 default: return null; | |
205 } | |
206 } | |
207 public override void EndWrite(IAsyncResult asyncResult) { | |
208 switch (Mode) { | |
209 case HTTPResponseStreamMode.Direct: | |
210 OutputStream.EndWrite(asyncResult); | |
211 break; | |
212 } | |
213 } | |
214 public override void SetLength(long value) { | |
215 switch (Mode) { | |
216 case HTTPResponseStreamMode.Buffered: | |
217 case HTTPResponseStreamMode.Hybrid: | |
218 if (value != 0) throw new InvalidOperationException("The length can only be set to zero using this method"); | |
219 Buffer = null; | |
220 break; | |
221 default: throw new InvalidOperationException("The operation is not supported in the current mode"); | |
222 } | |
223 } | |
224 public override long Length { | |
225 get { return MaxLength == -1 ? Position : MaxLength; } | |
226 } | |
227 public override long Position { | |
228 get { return (Buffer == null) ? BytesWritten : BytesWritten + Buffer.Length; } | |
229 set { throw new NotSupportedException(); } | |
230 } | |
231 public override void Flush() { | |
232 switch (Mode) { | |
233 case HTTPResponseStreamMode.Hybrid: | |
234 HybridSwitchToChunked(); | |
235 break; | |
236 } | |
237 } | |
238 public override bool CanWrite { | |
239 get { return Context.State != HTTPConnectionState.Completed && Context.State != HTTPConnectionState.Closed && (OutputStream == null || OutputStream.CanWrite); } | |
240 } | |
241 protected override void Dispose(bool disposing) { | |
242 base.Dispose(disposing); | |
243 if (disposing) { | |
244 switch (Mode) { | |
245 case HTTPResponseStreamMode.Direct: | |
246 /*if (MaxLength != -1 && MaxLength > BytesWritten) { | |
247 long left = MaxLength - BytesWritten; | |
248 Byte[] dummy = new Byte[Math.Min(left, 1024)]; | |
249 for (; left > 0; left -= dummy.Length) OutputStream.Write(dummy, 0, (int)Math.Min(left, dummy.Length)); | |
250 }*/ | |
251 break; | |
252 case HTTPResponseStreamMode.Chunked: | |
253 WriteChunked(null, 0, 0); | |
254 Mode = HTTPResponseStreamMode.None; | |
255 break; | |
256 case HTTPResponseStreamMode.Buffered: | |
257 case HTTPResponseStreamMode.Hybrid: | |
258 long length = (Buffer == null) ? 0 : Buffer.Length; | |
259 Context.SendHeader("Content-Length", length.ToString()); | |
260 OutputStream = Context.BeginResponseData(); | |
261 if (Buffer != null) Buffer.WriteTo(OutputStream); | |
262 Buffer = null; | |
263 Mode = HTTPResponseStreamMode.None; | |
264 break; | |
265 } | |
266 Context.EndResponseData(); | |
267 if (MaxLength != -1 && MaxLength > BytesWritten) Context.Close(); | |
268 } | |
269 } | |
270 public override bool CanTimeout { | |
271 get { return OutputStream == null ? true : OutputStream.CanTimeout; } | |
272 } | |
273 public override int WriteTimeout { | |
274 get { return OutputStream == null ? 0 : OutputStream.WriteTimeout; } | |
275 set { if (OutputStream != null) OutputStream.WriteTimeout = value; } | |
276 } | |
277 | |
278 public override bool CanRead { get { return false; } } | |
279 public override bool CanSeek { get { return false; } } | |
280 | |
281 public override int Read(byte[] buffer, int offset, int count) { throw new NotSupportedException(); } | |
282 public override long Seek(long offset, SeekOrigin origin) { throw new NotSupportedException(); } | |
283 } | |
284 private class HTTPInputStream : Stream { | |
285 public HTTPResponseStreamMode Mode { get; private set; } | |
286 private Stream InputStream = null; | |
287 private long BytesRead = 0; | |
288 private long BytesLeft = 0; | |
289 public HTTPInputStream(HTTPContext context) { | |
290 String TransferEncoding = context.GetRequestHeader("Transfer-Encoding"); | |
291 String ContentLength = context.GetRequestHeader("Content-Length"); | |
292 InputStream = context.Reader; | |
293 if (TransferEncoding != null && TransferEncoding.StartsWith("chunked", StringComparison.InvariantCultureIgnoreCase)) { | |
294 Mode = HTTPResponseStreamMode.Chunked; | |
295 } else if (ContentLength != null) { | |
296 Mode = HTTPResponseStreamMode.Direct; | |
297 if (!long.TryParse(ContentLength, out BytesLeft)) BytesLeft = 0; | |
298 } else { | |
299 Mode = HTTPResponseStreamMode.None; | |
300 } | |
301 } | |
302 private int ReadDirect(Byte[] buffer, int offset, int count) { | |
303 if (count > BytesLeft) count = (int)BytesLeft; | |
304 if (count == 0) return 0; | |
305 int read = InputStream.Read(buffer, offset, count); | |
306 if (read >= 0) { | |
307 BytesRead += read; | |
308 BytesLeft -= read; | |
309 } | |
310 return read; | |
311 } | |
312 public override int Read(byte[] buffer, int offset, int count) { | |
313 if (offset < 0 || count < 0 || offset + count > buffer.Length) throw new ArgumentOutOfRangeException("buffer", "Offset and count arguments exceed the buffer dimensions"); | |
314 switch (Mode) { | |
315 case HTTPResponseStreamMode.None: | |
316 return 0; | |
317 case HTTPResponseStreamMode.Direct: | |
318 return ReadDirect(buffer, offset, count); | |
319 case HTTPResponseStreamMode.Chunked: | |
320 if (BytesLeft == 0) { | |
321 String length = ReadLine(InputStream); | |
322 if (!long.TryParse(length, out BytesLeft)) BytesLeft = 0; | |
323 if (BytesLeft == 0) { | |
324 while (true) { | |
325 String line = ReadLine(InputStream); | |
326 if (line == null || line.Length == 0) break; | |
327 } | |
328 Mode = HTTPResponseStreamMode.None; | |
329 return 0; | |
330 } | |
331 } | |
332 return ReadDirect(buffer, offset, count); | |
333 default: | |
334 return 0; | |
335 } | |
336 } | |
337 class CompletedAsyncResult : AsyncResultBase { | |
338 public int Count { get; private set; } | |
339 public CompletedAsyncResult(AsyncCallback callback, Object state, int count) | |
340 : base(callback, state) { | |
341 this.Count = count; | |
342 SetCompleted(true, null); | |
343 } | |
344 } | |
345 public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback callback, object state) { | |
346 if (BytesLeft > 0) { | |
347 if (count > BytesLeft) count = (int)BytesLeft; | |
348 if (count == 0) return new CompletedAsyncResult(callback, state, 0); | |
349 return InputStream.BeginRead(buffer, offset, count, callback, state); | |
350 } else { | |
351 int ret = Read(buffer, offset, count); | |
352 return new CompletedAsyncResult(callback, state, ret); | |
353 } | |
354 } | |
355 public override int EndRead(IAsyncResult asyncResult) { | |
356 CompletedAsyncResult car = asyncResult as CompletedAsyncResult; | |
357 if (car != null) { | |
358 return car.Count; | |
359 } else { | |
360 return InputStream.EndRead(asyncResult); | |
361 } | |
362 } | |
363 public override long Length { | |
364 get { return BytesRead + BytesLeft; } | |
365 } | |
366 public override long Position { | |
367 get { return BytesRead; } | |
368 set { throw new NotSupportedException(); } | |
369 } | |
370 public override bool CanRead { | |
371 get { return (BytesLeft > 0 || Mode == HTTPResponseStreamMode.Chunked) && InputStream.CanRead; } | |
372 } | |
373 public override bool CanTimeout { | |
374 get { return InputStream.CanTimeout; } | |
375 } | |
376 public override int ReadTimeout { | |
377 get { return InputStream.ReadTimeout; } | |
378 set { InputStream.ReadTimeout = value; } | |
379 } | |
380 protected override void Dispose(bool disposing) { | |
381 base.Dispose(disposing); | |
382 Byte[] dummy = new Byte[1024]; | |
383 while (Read(dummy, 0, dummy.Length) != 0) ; | |
384 } | |
385 | |
386 public override bool CanSeek { get { return false; } } | |
387 public override bool CanWrite { get { return false; } } | |
388 public override void Flush() { } | |
389 public override long Seek(long offset, SeekOrigin origin) { throw new NotSupportedException(); } | |
390 public override void SetLength(long value) { throw new NotSupportedException(); } | |
391 public override void Write(byte[] buffer, int offset, int count) { throw new NotSupportedException(); } | |
3 | 392 } |
393 | |
10 | 394 public HTTPContext(HTTPServer server, TCPStream stream) : this(server, stream, stream.Socket) { } |
395 public HTTPContext(HTTPServer server, Socket socket) : this(server, null, socket) { } | |
396 public HTTPContext(HTTPServer server, Stream stream, Socket socket) { | |
3 | 397 this.Server = server; |
10 | 398 this.Socket = socket; |
399 if (socket != null) { | |
400 this.LocalEndPoint = socket.LocalEndPoint; | |
401 this.RemoteEndPoint = socket.RemoteEndPoint; | |
45
8df7f4dc5615
HTTP Server: enable KeepAlive option on TCP sockets
Ivo Smits <Ivo@UCIS.nl>
parents:
16
diff
changeset
|
402 if (socket.ProtocolType == ProtocolType.Tcp) socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.KeepAlive, true); |
10 | 403 if (stream == null) stream = new NetworkStream(socket, true); |
404 } | |
6 | 405 Init(stream); |
3 | 406 } |
407 | |
6 | 408 private void Init(Stream Stream) { |
3 | 409 Writer = new StreamWriter(Stream, Encoding.ASCII); |
410 Writer.NewLine = "\r\n"; | |
411 Writer.AutoFlush = true; | |
6 | 412 Reader = new PrebufferingStream(Stream); |
413 Reader.BeginPrebuffering(PrebufferCallback, null); | |
3 | 414 } |
415 | |
75 | 416 private static String ReadLine(Stream stream) { |
3 | 417 StringBuilder s = new StringBuilder(); |
418 while (true) { | |
75 | 419 int b = stream.ReadByte(); |
3 | 420 if (b == -1) { |
4
9e2e6433f57a
Small fix for HTTP Server - Flash Cross Domain Policy File support
Ivo Smits <Ivo@UCIS.nl>
parents:
3
diff
changeset
|
421 if (s.Length == 0) return null; |
3 | 422 break; |
423 } else if (b == 13) { | |
4
9e2e6433f57a
Small fix for HTTP Server - Flash Cross Domain Policy File support
Ivo Smits <Ivo@UCIS.nl>
parents:
3
diff
changeset
|
424 } else if (b == 10 || b == 0) { |
3 | 425 break; |
426 } else { | |
427 s.Append((Char)b); | |
428 } | |
429 } | |
430 return s.ToString(); | |
431 } | |
75 | 432 private String ReadLine() { |
433 return ReadLine(Reader); | |
434 } | |
3 | 435 |
6 | 436 private void PrebufferCallback(IAsyncResult ar) { |
3 | 437 State = HTTPConnectionState.ReceivingRequest; |
438 try { | |
6 | 439 Reader.EndPrebuffering(ar); |
3 | 440 String line = ReadLine(); |
441 if (line == null) { | |
442 Close(); | |
443 return; | |
444 } | |
445 if (Server.ServeFlashPolicyFile && line[0] == '<') { //<policy-file-request/> | |
446 Writer.WriteLine("<cross-domain-policy><allow-access-from domain=\"*\" to-ports=\"*\" /></cross-domain-policy>"); | |
6 | 447 Reader.WriteByte(0); |
3 | 448 Close(); |
449 return; | |
450 } | |
451 String[] request = line.Split(' '); | |
452 if (request.Length != 3) goto SendError400AndClose; | |
453 RequestMethod = request[0]; | |
454 String RequestAddress = request[1]; | |
455 switch (request[2]) { | |
456 case "HTTP/1.0": HTTPVersion = 10; break; | |
457 case "HTTP/1.1": HTTPVersion = 11; break; | |
75 | 458 default: goto SendError505AndClose; |
3 | 459 } |
460 request = RequestAddress.Split(new Char[] { '?' }); | |
461 RequestPath = Uri.UnescapeDataString(request[0]); | |
462 RequestQuery = request.Length > 1 ? request[1] : null; | |
463 RequestHeaders = new List<HTTPHeader>(); | |
464 while (true) { | |
465 line = ReadLine(); | |
466 if (line == null) goto SendError400AndClose; | |
467 if (line.Length == 0) break; | |
468 request = line.Split(new String[] { ": " }, 2, StringSplitOptions.None); | |
469 if (request.Length != 2) goto SendError400AndClose; | |
470 RequestHeaders.Add(new HTTPHeader(request[0], request[1])); | |
471 } | |
472 IHTTPContentProvider content = Server.ContentProvider; | |
473 if (content == null) goto SendError500AndClose; | |
474 State = HTTPConnectionState.ProcessingRequest; | |
475 content.ServeRequest(this); | |
75 | 476 Close(); |
3 | 477 } catch (Exception ex) { |
478 Console.Error.WriteLine(ex); | |
479 switch (State) { | |
480 case HTTPConnectionState.ProcessingRequest: goto SendError500AndClose; | |
481 default: | |
482 Close(); | |
483 break; | |
484 } | |
485 } | |
486 return; | |
487 | |
54
ba4e2cb031e0
Added general purpose tar archive reader class
Ivo Smits <Ivo@UCIS.nl>
parents:
45
diff
changeset
|
488 SendError400AndClose: |
7
4b78cc5f116b
Fixes and improvements (some untested)
Ivo Smits <Ivo@UCIS.nl>
parents:
6
diff
changeset
|
489 State = HTTPConnectionState.ProcessingRequest; |
3 | 490 SendErrorAndClose(400); |
491 return; | |
54
ba4e2cb031e0
Added general purpose tar archive reader class
Ivo Smits <Ivo@UCIS.nl>
parents:
45
diff
changeset
|
492 SendError500AndClose: |
7
4b78cc5f116b
Fixes and improvements (some untested)
Ivo Smits <Ivo@UCIS.nl>
parents:
6
diff
changeset
|
493 State = HTTPConnectionState.ProcessingRequest; |
3 | 494 SendErrorAndClose(500); |
495 return; | |
75 | 496 SendError505AndClose: |
497 State = HTTPConnectionState.ProcessingRequest; | |
498 SendErrorAndClose(400); | |
499 return; | |
3 | 500 } |
501 | |
502 public String GetRequestHeader(String name) { | |
503 if (State != HTTPConnectionState.ProcessingRequest && State != HTTPConnectionState.SendingHeaders && State != HTTPConnectionState.SendingContent) throw new InvalidOperationException(); | |
504 foreach (HTTPHeader h in RequestHeaders) { | |
505 if (name.Equals(h.Key, StringComparison.OrdinalIgnoreCase)) return h.Value; | |
506 } | |
507 return null; | |
508 } | |
509 public String[] GetRequestHeaders(String name) { | |
510 if (State != HTTPConnectionState.ProcessingRequest && State != HTTPConnectionState.SendingHeaders && State != HTTPConnectionState.SendingContent) throw new InvalidOperationException(); | |
511 String[] items = new String[0]; | |
512 foreach (HTTPHeader h in RequestHeaders) { | |
513 if (name.Equals(h.Key, StringComparison.OrdinalIgnoreCase)) ArrayUtil.Add(ref items, h.Value); | |
514 } | |
515 return items; | |
516 } | |
517 | |
75 | 518 private static String UnescapeUrlDataString(String text) { |
519 return Uri.UnescapeDataString(text.Replace('+', ' ')); | |
520 } | |
521 private static KeyValuePair<String, String>[] DecodeUrlEncodedFields(String data) { | |
522 List<KeyValuePair<string, string>> list = new List<KeyValuePair<string, string>>(); | |
523 foreach (String arg in data.Split('&')) { | |
524 String[] parts = arg.Split(new Char[] { '=' }, 2); | |
525 String key = UnescapeUrlDataString(parts[0]); | |
526 String value = (parts.Length > 1) ? UnescapeUrlDataString(parts[1]) : String.Empty; | |
527 list.Add(new KeyValuePair<string, string>(key, value)); | |
3 | 528 } |
75 | 529 return list.ToArray(); |
530 } | |
531 | |
532 public String GetQueryParameter(String name) { | |
533 foreach (KeyValuePair<String, String> kvp in GetQueryParameters()) if (kvp.Key == name) return kvp.Value; | |
534 return null; | |
535 } | |
536 public String[] GetQueryParameters(String name) { | |
537 List<String> list = new List<string>(); | |
538 foreach (KeyValuePair<String, String> kvp in GetQueryParameters()) if (kvp.Key == name) list.Add(kvp.Value); | |
539 return list.ToArray(); | |
540 } | |
541 public KeyValuePair<String, String>[] GetQueryParameters() { | |
542 if (QueryParameters == null) QueryParameters = DecodeUrlEncodedFields(RequestQuery); | |
543 return QueryParameters; | |
544 } | |
545 | |
546 public String GetPostParameter(String name) { | |
547 foreach (KeyValuePair<String, String> kvp in GetPostParameters()) if (kvp.Key == name) return kvp.Value; | |
548 return null; | |
549 } | |
550 public String[] GetPostParameters(String name) { | |
551 List<String> list = new List<string>(); | |
552 foreach (KeyValuePair<String, String> kvp in GetPostParameters()) if (kvp.Key == name) list.Add(kvp.Value); | |
553 return list.ToArray(); | |
554 } | |
555 public KeyValuePair<String, String>[] GetPostParameters() { | |
556 if (PostParameters == null) { | |
557 if (RequestMethod == "POST" && GetRequestHeader("Content-Type") == "application/x-www-form-urlencoded") { | |
558 String data; | |
559 using (StreamReader reader = new StreamReader(OpenRequestStream(), Encoding.UTF8)) data = reader.ReadToEnd(); | |
560 PostParameters = DecodeUrlEncodedFields(data); | |
561 } else { | |
562 PostParameters = new KeyValuePair<string, string>[0]; | |
563 } | |
564 } | |
565 return PostParameters; | |
566 } | |
567 | |
568 public Stream OpenRequestStream() { | |
569 if (RequestStream == null) RequestStream = new HTTPInputStream(this); | |
570 return RequestStream; | |
571 } | |
572 | |
573 private static String GetMessageForStatus(int code) { | |
574 switch (code) { | |
575 case 101: return "Switching Protocols"; | |
576 case 200: return "OK"; | |
577 case 301: return "Moved Permanently"; | |
578 case 302: return "Found"; | |
579 case 303: return "See Other"; | |
580 case 304: return "Not Modified"; | |
581 case 307: return "Temporary Redirect"; | |
582 case 400: return "Bad Request"; | |
583 case 401: return "Access denied"; | |
584 case 403: return "Forbidden"; | |
585 case 404: return "Not Found"; | |
586 case 500: return "Internal Server Error"; | |
587 case 505: return "HTTP Version Not Supported"; | |
588 default: return "Unknown Status"; | |
589 } | |
3 | 590 } |
591 | |
592 public void SendStatus(int code) { | |
75 | 593 String message = GetMessageForStatus(code); |
3 | 594 SendStatus(code, message); |
595 } | |
596 public void SendStatus(int code, String message) { | |
597 if (State != HTTPConnectionState.ProcessingRequest) throw new InvalidOperationException(); | |
598 StringBuilder sb = new StringBuilder(); | |
599 sb.Append("HTTP/"); | |
600 switch (HTTPVersion) { | |
601 case 10: sb.Append("1.0"); break; | |
602 case 11: sb.Append("1.1"); break; | |
75 | 603 default: sb.Append("1.0"); break; |
3 | 604 } |
605 sb.Append(" "); | |
606 sb.Append(code); | |
607 sb.Append(" "); | |
608 sb.Append(message); | |
609 Writer.WriteLine(sb.ToString()); | |
610 State = HTTPConnectionState.SendingHeaders; | |
611 if (!SuppressStandardHeaders) { | |
612 SendHeader("Expires", "Expires: Sun, 1 Jan 2000 00:00:00 GMT"); | |
613 SendHeader("Cache-Control", "no-store, no-cache, must-revalidate"); | |
614 SendHeader("Cache-Control", "post-check=0, pre-check=0"); | |
615 SendHeader("Pragma", "no-cache"); | |
75 | 616 SendHeader("Server", "UCIS Embedded Webserver"); |
3 | 617 SendHeader("Connection", "Close"); |
618 } | |
619 } | |
620 public void SendHeader(String name, String value) { | |
621 if (State == HTTPConnectionState.ProcessingRequest) SendStatus(200); | |
622 if (State != HTTPConnectionState.SendingHeaders) throw new InvalidOperationException(); | |
623 Writer.WriteLine(name + ": " + value); | |
624 } | |
75 | 625 public void SendErrorResponse(int state) { |
626 String message = GetMessageForStatus(state); | |
627 try { | |
628 SendStatus(state, message); | |
629 SendHeader("Content-Type", "text/plain"); | |
630 WriteResponseData(Encoding.ASCII.GetBytes(String.Format("Error {0}: {1}", state, message))); | |
631 } catch (Exception ex) { | |
632 Console.Error.WriteLine(ex); | |
633 } | |
634 } | |
635 public Stream OpenResponseStream(HTTPResponseStreamMode mode) { | |
636 if (ResponseStream != null) throw new InvalidOperationException("The response stream has already been opened"); | |
637 return ResponseStream = new HTTPOutputStream(this, mode); | |
638 } | |
639 public Stream OpenResponseStream(long length) { | |
640 if (ResponseStream != null) throw new InvalidOperationException("The response stream has already been opened"); | |
641 if (length < 0) throw new ArgumentException("Response length can not be negative", "length"); | |
642 return ResponseStream = new HTTPOutputStream(this, HTTPResponseStreamMode.Direct, length); | |
643 } | |
644 public void WriteResponseData(Byte[] buffer) { | |
645 WriteResponseData(buffer, 0, buffer.Length); | |
646 } | |
647 public void WriteResponseData(Byte[] buffer, int offset, int count) { | |
648 if (offset < 0 || count < 0 || offset + count > buffer.Length) throw new ArgumentOutOfRangeException("buffer", "Offset and count arguments exceed the buffer dimensions"); | |
649 SendHeader("Content-Length", count.ToString()); | |
650 Stream stream = BeginResponseData(); | |
651 stream.Write(buffer, offset, count); | |
652 EndResponseData(); | |
653 } | |
654 private Stream BeginResponseData() { | |
3 | 655 if (State == HTTPConnectionState.ProcessingRequest) SendStatus(200); |
656 if (State == HTTPConnectionState.SendingHeaders) { | |
657 Writer.WriteLine(); | |
658 State = HTTPConnectionState.SendingContent; | |
659 } | |
75 | 660 if (State != HTTPConnectionState.SendingContent) throw new InvalidOperationException("The response stream can not be opened in the current state"); |
661 return Reader; | |
662 } | |
663 private void EndResponseData() { | |
664 if (State == HTTPConnectionState.Completed || State == HTTPConnectionState.Closed) return; | |
665 OpenRequestStream().Close(); | |
666 if (State != HTTPConnectionState.SendingContent) WriteResponseData(new Byte[0]); | |
667 State = HTTPConnectionState.Completed; | |
668 } | |
669 | |
670 public Stream GetDirectStream() { | |
671 if (State == HTTPConnectionState.Closed) throw new InvalidOperationException("The context has been closed"); | |
672 BeginResponseData(); | |
673 State = HTTPConnectionState.Closed; | |
6 | 674 return Reader; |
3 | 675 } |
676 | |
75 | 677 private void SendErrorAndClose(int code) { |
678 SendErrorResponse(code); | |
679 Close(); | |
680 } | |
681 private void Close() { | |
3 | 682 if (State == HTTPConnectionState.Closed) return; |
6 | 683 Reader.Close(); |
3 | 684 State = HTTPConnectionState.Closed; |
685 } | |
0 | 686 } |
687 | |
3 | 688 public interface IHTTPContentProvider { |
689 void ServeRequest(HTTPContext context); | |
690 } | |
12 | 691 public delegate void HTTPContentProviderDelegate(HTTPContext context); |
692 public class HTTPContentProviderFunction : IHTTPContentProvider { | |
693 public HTTPContentProviderDelegate Handler { get; private set; } | |
694 public HTTPContentProviderFunction(HTTPContentProviderDelegate handler) { | |
695 this.Handler = handler; | |
696 } | |
697 public void ServeRequest(HTTPContext context) { | |
698 Handler(context); | |
699 } | |
700 } | |
3 | 701 public class HTTPPathSelector : IHTTPContentProvider { |
702 private List<KeyValuePair<String, IHTTPContentProvider>> Prefixes; | |
703 private StringComparison PrefixComparison; | |
704 public HTTPPathSelector() : this(false) { } | |
705 public HTTPPathSelector(Boolean caseSensitive) { | |
706 Prefixes = new List<KeyValuePair<string, IHTTPContentProvider>>(); | |
707 PrefixComparison = caseSensitive ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase; | |
708 } | |
709 public void AddPrefix(String prefix, IHTTPContentProvider contentProvider) { | |
710 Prefixes.Add(new KeyValuePair<string, IHTTPContentProvider>(prefix, contentProvider)); | |
711 } | |
712 public void DeletePrefix(String prefix) { | |
713 Prefixes.RemoveAll(delegate(KeyValuePair<string, IHTTPContentProvider> item) { return prefix.Equals(item.Key, PrefixComparison); }); | |
714 } | |
715 public void ServeRequest(HTTPContext context) { | |
716 KeyValuePair<string, IHTTPContentProvider> c = Prefixes.Find(delegate(KeyValuePair<string, IHTTPContentProvider> item) { return context.RequestPath.StartsWith(item.Key, PrefixComparison); }); | |
717 if (c.Value != null) { | |
718 c.Value.ServeRequest(context); | |
719 } else { | |
75 | 720 context.SendErrorResponse(404); |
3 | 721 } |
722 } | |
723 } | |
7
4b78cc5f116b
Fixes and improvements (some untested)
Ivo Smits <Ivo@UCIS.nl>
parents:
6
diff
changeset
|
724 public class HTTPStaticContent : IHTTPContentProvider { |
4b78cc5f116b
Fixes and improvements (some untested)
Ivo Smits <Ivo@UCIS.nl>
parents:
6
diff
changeset
|
725 public ArraySegment<Byte> ContentBuffer { get; set; } |
4b78cc5f116b
Fixes and improvements (some untested)
Ivo Smits <Ivo@UCIS.nl>
parents:
6
diff
changeset
|
726 public String ContentType { get; set; } |
4b78cc5f116b
Fixes and improvements (some untested)
Ivo Smits <Ivo@UCIS.nl>
parents:
6
diff
changeset
|
727 public HTTPStaticContent() : this(new ArraySegment<Byte>()) { } |
4b78cc5f116b
Fixes and improvements (some untested)
Ivo Smits <Ivo@UCIS.nl>
parents:
6
diff
changeset
|
728 public HTTPStaticContent(ArraySegment<Byte> content) : this(content, "application/octet-stream") { } |
4b78cc5f116b
Fixes and improvements (some untested)
Ivo Smits <Ivo@UCIS.nl>
parents:
6
diff
changeset
|
729 public HTTPStaticContent(String content, String contentType) : this(Encoding.UTF8.GetBytes(content), contentType) { } |
4b78cc5f116b
Fixes and improvements (some untested)
Ivo Smits <Ivo@UCIS.nl>
parents:
6
diff
changeset
|
730 public HTTPStaticContent(String contentType) : this(new ArraySegment<Byte>(), contentType) { } |
4b78cc5f116b
Fixes and improvements (some untested)
Ivo Smits <Ivo@UCIS.nl>
parents:
6
diff
changeset
|
731 public HTTPStaticContent(Byte[] content, String contentType) : this(new ArraySegment<Byte>(content), contentType) { } |
4b78cc5f116b
Fixes and improvements (some untested)
Ivo Smits <Ivo@UCIS.nl>
parents:
6
diff
changeset
|
732 public HTTPStaticContent(ArraySegment<Byte> content, String contentType) { |
4b78cc5f116b
Fixes and improvements (some untested)
Ivo Smits <Ivo@UCIS.nl>
parents:
6
diff
changeset
|
733 this.ContentBuffer = content; |
4b78cc5f116b
Fixes and improvements (some untested)
Ivo Smits <Ivo@UCIS.nl>
parents:
6
diff
changeset
|
734 this.ContentType = contentType; |
4b78cc5f116b
Fixes and improvements (some untested)
Ivo Smits <Ivo@UCIS.nl>
parents:
6
diff
changeset
|
735 } |
4b78cc5f116b
Fixes and improvements (some untested)
Ivo Smits <Ivo@UCIS.nl>
parents:
6
diff
changeset
|
736 public void SetContent(Byte[] bytes) { ContentBuffer = new ArraySegment<byte>(bytes); } |
4b78cc5f116b
Fixes and improvements (some untested)
Ivo Smits <Ivo@UCIS.nl>
parents:
6
diff
changeset
|
737 public void SetContent(Byte[] bytes, int offset, int count) { ContentBuffer = new ArraySegment<byte>(bytes, offset, count); } |
4b78cc5f116b
Fixes and improvements (some untested)
Ivo Smits <Ivo@UCIS.nl>
parents:
6
diff
changeset
|
738 public void SetContent(String content, Encoding encoding) { SetContent(encoding.GetBytes(content)); } |
4b78cc5f116b
Fixes and improvements (some untested)
Ivo Smits <Ivo@UCIS.nl>
parents:
6
diff
changeset
|
739 public void SetContent(String content) { SetContent(content, Encoding.UTF8); } |
4b78cc5f116b
Fixes and improvements (some untested)
Ivo Smits <Ivo@UCIS.nl>
parents:
6
diff
changeset
|
740 public void ServeRequest(HTTPContext context) { |
4b78cc5f116b
Fixes and improvements (some untested)
Ivo Smits <Ivo@UCIS.nl>
parents:
6
diff
changeset
|
741 ArraySegment<Byte> content = ContentBuffer; |
4b78cc5f116b
Fixes and improvements (some untested)
Ivo Smits <Ivo@UCIS.nl>
parents:
6
diff
changeset
|
742 if (content.Array == null) { |
75 | 743 context.SendErrorResponse(404); |
7
4b78cc5f116b
Fixes and improvements (some untested)
Ivo Smits <Ivo@UCIS.nl>
parents:
6
diff
changeset
|
744 return; |
4b78cc5f116b
Fixes and improvements (some untested)
Ivo Smits <Ivo@UCIS.nl>
parents:
6
diff
changeset
|
745 } |
4b78cc5f116b
Fixes and improvements (some untested)
Ivo Smits <Ivo@UCIS.nl>
parents:
6
diff
changeset
|
746 String contentType = ContentType; |
4b78cc5f116b
Fixes and improvements (some untested)
Ivo Smits <Ivo@UCIS.nl>
parents:
6
diff
changeset
|
747 context.SendStatus(200); |
4b78cc5f116b
Fixes and improvements (some untested)
Ivo Smits <Ivo@UCIS.nl>
parents:
6
diff
changeset
|
748 if (contentType != null) context.SendHeader("Content-Type", contentType); |
75 | 749 context.WriteResponseData(content.Array, content.Offset, content.Count); |
7
4b78cc5f116b
Fixes and improvements (some untested)
Ivo Smits <Ivo@UCIS.nl>
parents:
6
diff
changeset
|
750 } |
4b78cc5f116b
Fixes and improvements (some untested)
Ivo Smits <Ivo@UCIS.nl>
parents:
6
diff
changeset
|
751 } |
3 | 752 public class HTTPFileProvider : IHTTPContentProvider { |
753 public String FileName { get; private set; } | |
754 public String ContentType { get; private set; } | |
755 public HTTPFileProvider(String fileName) : this(fileName, "application/octet-stream") { } | |
756 public HTTPFileProvider(String fileName, String contentType) { | |
757 this.FileName = fileName; | |
758 this.ContentType = contentType; | |
759 } | |
760 public void ServeRequest(HTTPContext context) { | |
761 if (File.Exists(FileName)) { | |
762 using (FileStream fs = File.OpenRead(FileName)) { | |
763 context.SendStatus(200); | |
764 context.SendHeader("Content-Type", ContentType); | |
765 long left = fs.Length; | |
75 | 766 Stream response = context.OpenResponseStream(fs.Length); |
3 | 767 byte[] buffer = new byte[1024 * 10]; |
768 while (fs.CanRead) { | |
769 int len = fs.Read(buffer, 0, buffer.Length); | |
770 if (len <= 0) break; | |
771 left -= len; | |
772 response.Write(buffer, 0, len); | |
773 } | |
774 response.Close(); | |
775 } | |
776 } else { | |
75 | 777 context.SendErrorResponse(404); |
3 | 778 } |
779 } | |
0 | 780 } |
3 | 781 public class HTTPUnTarchiveProvider : IHTTPContentProvider { |
782 public String TarFileName { get; private set; } | |
783 public HTTPUnTarchiveProvider(String tarFile) { | |
784 this.TarFileName = tarFile; | |
785 } | |
786 public void ServeRequest(HTTPContext context) { | |
787 if (!File.Exists(TarFileName)) { | |
75 | 788 context.SendErrorResponse(404); |
3 | 789 return; |
790 } | |
791 String reqname1 = context.RequestPath; | |
54
ba4e2cb031e0
Added general purpose tar archive reader class
Ivo Smits <Ivo@UCIS.nl>
parents:
45
diff
changeset
|
792 if (reqname1.StartsWith("/")) reqname1 = reqname1.Substring(1); |
3 | 793 String reqname2 = reqname1; |
794 if (reqname2.Length > 0 && !reqname2.EndsWith("/")) reqname2 += "/"; | |
795 reqname2 += "index.htm"; | |
54
ba4e2cb031e0
Added general purpose tar archive reader class
Ivo Smits <Ivo@UCIS.nl>
parents:
45
diff
changeset
|
796 foreach (TarchiveEntry file in new TarchiveReader(TarFileName)) { |
ba4e2cb031e0
Added general purpose tar archive reader class
Ivo Smits <Ivo@UCIS.nl>
parents:
45
diff
changeset
|
797 if (!file.IsFile) continue; |
ba4e2cb031e0
Added general purpose tar archive reader class
Ivo Smits <Ivo@UCIS.nl>
parents:
45
diff
changeset
|
798 if (!reqname1.Equals(file.Name, StringComparison.OrdinalIgnoreCase) && !reqname2.Equals(file.Name, StringComparison.OrdinalIgnoreCase)) continue; |
ba4e2cb031e0
Added general purpose tar archive reader class
Ivo Smits <Ivo@UCIS.nl>
parents:
45
diff
changeset
|
799 context.SendStatus(200); |
ba4e2cb031e0
Added general purpose tar archive reader class
Ivo Smits <Ivo@UCIS.nl>
parents:
45
diff
changeset
|
800 String ctype = null; |
ba4e2cb031e0
Added general purpose tar archive reader class
Ivo Smits <Ivo@UCIS.nl>
parents:
45
diff
changeset
|
801 switch (Path.GetExtension(file.Name).ToLowerInvariant()) { |
ba4e2cb031e0
Added general purpose tar archive reader class
Ivo Smits <Ivo@UCIS.nl>
parents:
45
diff
changeset
|
802 case ".txt": ctype = "text/plain"; break; |
ba4e2cb031e0
Added general purpose tar archive reader class
Ivo Smits <Ivo@UCIS.nl>
parents:
45
diff
changeset
|
803 case ".htm": |
ba4e2cb031e0
Added general purpose tar archive reader class
Ivo Smits <Ivo@UCIS.nl>
parents:
45
diff
changeset
|
804 case ".html": ctype = "text/html"; break; |
ba4e2cb031e0
Added general purpose tar archive reader class
Ivo Smits <Ivo@UCIS.nl>
parents:
45
diff
changeset
|
805 case ".css": ctype = "text/css"; break; |
ba4e2cb031e0
Added general purpose tar archive reader class
Ivo Smits <Ivo@UCIS.nl>
parents:
45
diff
changeset
|
806 case ".js": ctype = "application/x-javascript"; break; |
ba4e2cb031e0
Added general purpose tar archive reader class
Ivo Smits <Ivo@UCIS.nl>
parents:
45
diff
changeset
|
807 case ".png": ctype = "image/png"; break; |
ba4e2cb031e0
Added general purpose tar archive reader class
Ivo Smits <Ivo@UCIS.nl>
parents:
45
diff
changeset
|
808 case ".jpg": |
ba4e2cb031e0
Added general purpose tar archive reader class
Ivo Smits <Ivo@UCIS.nl>
parents:
45
diff
changeset
|
809 case ".jpeg": ctype = "image/jpeg"; break; |
ba4e2cb031e0
Added general purpose tar archive reader class
Ivo Smits <Ivo@UCIS.nl>
parents:
45
diff
changeset
|
810 case ".gif": ctype = "image/gif"; break; |
ba4e2cb031e0
Added general purpose tar archive reader class
Ivo Smits <Ivo@UCIS.nl>
parents:
45
diff
changeset
|
811 case ".ico": ctype = "image/x-icon"; break; |
ba4e2cb031e0
Added general purpose tar archive reader class
Ivo Smits <Ivo@UCIS.nl>
parents:
45
diff
changeset
|
812 } |
ba4e2cb031e0
Added general purpose tar archive reader class
Ivo Smits <Ivo@UCIS.nl>
parents:
45
diff
changeset
|
813 if (ctype != null) context.SendHeader("Content-Type", ctype); |
75 | 814 using (Stream response = context.OpenResponseStream(file.Size), source = file.GetStream()) { |
54
ba4e2cb031e0
Added general purpose tar archive reader class
Ivo Smits <Ivo@UCIS.nl>
parents:
45
diff
changeset
|
815 byte[] buffer = new byte[Math.Min(source.Length, 1024 * 10)]; |
ba4e2cb031e0
Added general purpose tar archive reader class
Ivo Smits <Ivo@UCIS.nl>
parents:
45
diff
changeset
|
816 while (source.CanRead) { |
ba4e2cb031e0
Added general purpose tar archive reader class
Ivo Smits <Ivo@UCIS.nl>
parents:
45
diff
changeset
|
817 int len = source.Read(buffer, 0, buffer.Length); |
ba4e2cb031e0
Added general purpose tar archive reader class
Ivo Smits <Ivo@UCIS.nl>
parents:
45
diff
changeset
|
818 if (len <= 0) break; |
ba4e2cb031e0
Added general purpose tar archive reader class
Ivo Smits <Ivo@UCIS.nl>
parents:
45
diff
changeset
|
819 response.Write(buffer, 0, len); |
3 | 820 } |
821 } | |
54
ba4e2cb031e0
Added general purpose tar archive reader class
Ivo Smits <Ivo@UCIS.nl>
parents:
45
diff
changeset
|
822 return; |
3 | 823 } |
75 | 824 context.SendErrorResponse(404); |
3 | 825 } |
0 | 826 } |
827 } |