comparison Net/HTTP.cs @ 75:50d4aed66c67

Improved HTTP classes
author Ivo Smits <Ivo@UCIS.nl>
date Mon, 03 Feb 2014 22:53:31 +0100
parents ba4e2cb031e0
children 146a8d224d86 3c1bba376dca
comparison
equal deleted inserted replaced
73:6aca18ee4ec6 75:50d4aed66c67
68 new HTTPContext(this, stream); 68 new HTTPContext(this, stream);
69 return false; 69 return false;
70 } 70 }
71 } 71 }
72 72
73 public enum HTTPResponseStreamMode {
74 None = -1,
75 Direct = 0,
76 Buffered = 1,
77 Chunked = 2,
78 Hybrid = 3,
79 }
80
73 public class HTTPContext { 81 public class HTTPContext {
74 public HTTPServer Server { get; private set; } 82 public HTTPServer Server { get; private set; }
75 public EndPoint LocalEndPoint { get; private set; } 83 public EndPoint LocalEndPoint { get; private set; }
76 public EndPoint RemoteEndPoint { get; private set; } 84 public EndPoint RemoteEndPoint { get; private set; }
77 85
86 94
87 private StreamWriter Writer; 95 private StreamWriter Writer;
88 private PrebufferingStream Reader; 96 private PrebufferingStream Reader;
89 private List<HTTPHeader> RequestHeaders; 97 private List<HTTPHeader> RequestHeaders;
90 private HTTPConnectionState State = HTTPConnectionState.Starting; 98 private HTTPConnectionState State = HTTPConnectionState.Starting;
91 99 private KeyValuePair<String, String>[] QueryParameters = null, PostParameters = null;
100 private HTTPOutputStream ResponseStream = null;
101 private HTTPInputStream RequestStream = null;
92 102
93 private enum HTTPConnectionState { 103 private enum HTTPConnectionState {
94 Starting = 0, 104 Starting = 0,
95 ReceivingRequest = 1, 105 ReceivingRequest = 1,
96 ProcessingRequest = 2, 106 ProcessingRequest = 2,
97 SendingHeaders = 3, 107 SendingHeaders = 3,
98 SendingContent = 4, 108 SendingContent = 4,
99 Closed = 5, 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;
159 oldbuffer.WriteTo(this);
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(); }
100 } 392 }
101 393
102 public HTTPContext(HTTPServer server, TCPStream stream) : this(server, stream, stream.Socket) { } 394 public HTTPContext(HTTPServer server, TCPStream stream) : this(server, stream, stream.Socket) { }
103 public HTTPContext(HTTPServer server, Socket socket) : this(server, null, socket) { } 395 public HTTPContext(HTTPServer server, Socket socket) : this(server, null, socket) { }
104 public HTTPContext(HTTPServer server, Stream stream, Socket socket) { 396 public HTTPContext(HTTPServer server, Stream stream, Socket socket) {
119 Writer.AutoFlush = true; 411 Writer.AutoFlush = true;
120 Reader = new PrebufferingStream(Stream); 412 Reader = new PrebufferingStream(Stream);
121 Reader.BeginPrebuffering(PrebufferCallback, null); 413 Reader.BeginPrebuffering(PrebufferCallback, null);
122 } 414 }
123 415
124 private String ReadLine() { 416 private static String ReadLine(Stream stream) {
125 StringBuilder s = new StringBuilder(); 417 StringBuilder s = new StringBuilder();
126 while (true) { 418 while (true) {
127 int b = Reader.ReadByte(); 419 int b = stream.ReadByte();
128 if (b == -1) { 420 if (b == -1) {
129 if (s.Length == 0) return null; 421 if (s.Length == 0) return null;
130 break; 422 break;
131 } else if (b == 13) { 423 } else if (b == 13) {
132 } else if (b == 10 || b == 0) { 424 } else if (b == 10 || b == 0) {
135 s.Append((Char)b); 427 s.Append((Char)b);
136 } 428 }
137 } 429 }
138 return s.ToString(); 430 return s.ToString();
139 } 431 }
432 private String ReadLine() {
433 return ReadLine(Reader);
434 }
140 435
141 private void PrebufferCallback(IAsyncResult ar) { 436 private void PrebufferCallback(IAsyncResult ar) {
142 State = HTTPConnectionState.ReceivingRequest; 437 State = HTTPConnectionState.ReceivingRequest;
143 try { 438 try {
144 Reader.EndPrebuffering(ar); 439 Reader.EndPrebuffering(ar);
158 RequestMethod = request[0]; 453 RequestMethod = request[0];
159 String RequestAddress = request[1]; 454 String RequestAddress = request[1];
160 switch (request[2]) { 455 switch (request[2]) {
161 case "HTTP/1.0": HTTPVersion = 10; break; 456 case "HTTP/1.0": HTTPVersion = 10; break;
162 case "HTTP/1.1": HTTPVersion = 11; break; 457 case "HTTP/1.1": HTTPVersion = 11; break;
163 default: goto SendError400AndClose; 458 default: goto SendError505AndClose;
164 } 459 }
165 request = RequestAddress.Split(new Char[] { '?' }); 460 request = RequestAddress.Split(new Char[] { '?' });
166 RequestPath = Uri.UnescapeDataString(request[0]); 461 RequestPath = Uri.UnescapeDataString(request[0]);
167 RequestQuery = request.Length > 1 ? request[1] : null; 462 RequestQuery = request.Length > 1 ? request[1] : null;
168 RequestHeaders = new List<HTTPHeader>(); 463 RequestHeaders = new List<HTTPHeader>();
176 } 471 }
177 IHTTPContentProvider content = Server.ContentProvider; 472 IHTTPContentProvider content = Server.ContentProvider;
178 if (content == null) goto SendError500AndClose; 473 if (content == null) goto SendError500AndClose;
179 State = HTTPConnectionState.ProcessingRequest; 474 State = HTTPConnectionState.ProcessingRequest;
180 content.ServeRequest(this); 475 content.ServeRequest(this);
476 Close();
181 } catch (Exception ex) { 477 } catch (Exception ex) {
182 Console.Error.WriteLine(ex); 478 Console.Error.WriteLine(ex);
183 switch (State) { 479 switch (State) {
184 case HTTPConnectionState.ProcessingRequest: goto SendError500AndClose; 480 case HTTPConnectionState.ProcessingRequest: goto SendError500AndClose;
185 default: 481 default:
195 return; 491 return;
196 SendError500AndClose: 492 SendError500AndClose:
197 State = HTTPConnectionState.ProcessingRequest; 493 State = HTTPConnectionState.ProcessingRequest;
198 SendErrorAndClose(500); 494 SendErrorAndClose(500);
199 return; 495 return;
496 SendError505AndClose:
497 State = HTTPConnectionState.ProcessingRequest;
498 SendErrorAndClose(400);
499 return;
200 } 500 }
201 501
202 public String GetRequestHeader(String name) { 502 public String GetRequestHeader(String name) {
203 if (State != HTTPConnectionState.ProcessingRequest && State != HTTPConnectionState.SendingHeaders && State != HTTPConnectionState.SendingContent) throw new InvalidOperationException(); 503 if (State != HTTPConnectionState.ProcessingRequest && State != HTTPConnectionState.SendingHeaders && State != HTTPConnectionState.SendingContent) throw new InvalidOperationException();
204 foreach (HTTPHeader h in RequestHeaders) { 504 foreach (HTTPHeader h in RequestHeaders) {
213 if (name.Equals(h.Key, StringComparison.OrdinalIgnoreCase)) ArrayUtil.Add(ref items, h.Value); 513 if (name.Equals(h.Key, StringComparison.OrdinalIgnoreCase)) ArrayUtil.Add(ref items, h.Value);
214 } 514 }
215 return items; 515 return items;
216 } 516 }
217 517
218 public void SendErrorAndClose(int state) { 518 private static String UnescapeUrlDataString(String text) {
219 try { 519 return Uri.UnescapeDataString(text.Replace('+', ' '));
220 SendStatus(state); 520 }
221 GetResponseStream(); 521 private static KeyValuePair<String, String>[] DecodeUrlEncodedFields(String data) {
222 } catch (Exception ex) { 522 List<KeyValuePair<string, string>> list = new List<KeyValuePair<string, string>>();
223 Console.Error.WriteLine(ex); 523 foreach (String arg in data.Split('&')) {
224 } 524 String[] parts = arg.Split(new Char[] { '=' }, 2);
225 Close(); 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));
528 }
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 }
226 } 590 }
227 591
228 public void SendStatus(int code) { 592 public void SendStatus(int code) {
229 String message; 593 String message = GetMessageForStatus(code);
230 switch (code) {
231 case 101: message = "Switching Protocols"; break;
232 case 200: message = "OK"; break;
233 case 400: message = "Bad Request"; break;
234 case 404: message = "Not Found"; break;
235 case 500: message = "Internal Server Error"; break;
236 default: message = "Unknown Status"; break;
237 }
238 SendStatus(code, message); 594 SendStatus(code, message);
239 } 595 }
240 public void SendStatus(int code, String message) { 596 public void SendStatus(int code, String message) {
241 if (State != HTTPConnectionState.ProcessingRequest) throw new InvalidOperationException(); 597 if (State != HTTPConnectionState.ProcessingRequest) throw new InvalidOperationException();
242 StringBuilder sb = new StringBuilder(); 598 StringBuilder sb = new StringBuilder();
243 sb.Append("HTTP/"); 599 sb.Append("HTTP/");
244 switch (HTTPVersion) { 600 switch (HTTPVersion) {
245 case 10: sb.Append("1.0"); break; 601 case 10: sb.Append("1.0"); break;
246 case 11: sb.Append("1.1"); break; 602 case 11: sb.Append("1.1"); break;
247 default: throw new ArgumentException("The HTTP version is not supported", "HTTPVersion"); 603 default: sb.Append("1.0"); break;
248 } 604 }
249 sb.Append(" "); 605 sb.Append(" ");
250 sb.Append(code); 606 sb.Append(code);
251 sb.Append(" "); 607 sb.Append(" ");
252 sb.Append(message); 608 sb.Append(message);
255 if (!SuppressStandardHeaders) { 611 if (!SuppressStandardHeaders) {
256 SendHeader("Expires", "Expires: Sun, 1 Jan 2000 00:00:00 GMT"); 612 SendHeader("Expires", "Expires: Sun, 1 Jan 2000 00:00:00 GMT");
257 SendHeader("Cache-Control", "no-store, no-cache, must-revalidate"); 613 SendHeader("Cache-Control", "no-store, no-cache, must-revalidate");
258 SendHeader("Cache-Control", "post-check=0, pre-check=0"); 614 SendHeader("Cache-Control", "post-check=0, pre-check=0");
259 SendHeader("Pragma", "no-cache"); 615 SendHeader("Pragma", "no-cache");
260 SendHeader("Server", "UCIS Webserver"); 616 SendHeader("Server", "UCIS Embedded Webserver");
261 SendHeader("Connection", "Close"); 617 SendHeader("Connection", "Close");
262 } 618 }
263 } 619 }
264 public void SendHeader(String name, String value) { 620 public void SendHeader(String name, String value) {
265 if (State == HTTPConnectionState.ProcessingRequest) SendStatus(200); 621 if (State == HTTPConnectionState.ProcessingRequest) SendStatus(200);
266 if (State != HTTPConnectionState.SendingHeaders) throw new InvalidOperationException(); 622 if (State != HTTPConnectionState.SendingHeaders) throw new InvalidOperationException();
267 Writer.WriteLine(name + ": " + value); 623 Writer.WriteLine(name + ": " + value);
268 } 624 }
269 public Stream GetResponseStream() { 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() {
270 if (State == HTTPConnectionState.ProcessingRequest) SendStatus(200); 655 if (State == HTTPConnectionState.ProcessingRequest) SendStatus(200);
271 if (State == HTTPConnectionState.SendingHeaders) { 656 if (State == HTTPConnectionState.SendingHeaders) {
272 Writer.WriteLine(); 657 Writer.WriteLine();
273 State = HTTPConnectionState.SendingContent; 658 State = HTTPConnectionState.SendingContent;
274 } 659 }
275 if (State != HTTPConnectionState.SendingContent) throw new InvalidOperationException(); 660 if (State != HTTPConnectionState.SendingContent) throw new InvalidOperationException("The response stream can not be opened in the current state");
276 return Reader; 661 return Reader;
277 } 662 }
278 663 private void EndResponseData() {
279 public void Close() { 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;
674 return Reader;
675 }
676
677 private void SendErrorAndClose(int code) {
678 SendErrorResponse(code);
679 Close();
680 }
681 private void Close() {
280 if (State == HTTPConnectionState.Closed) return; 682 if (State == HTTPConnectionState.Closed) return;
281 Reader.Close(); 683 Reader.Close();
282 State = HTTPConnectionState.Closed; 684 State = HTTPConnectionState.Closed;
283 } 685 }
284 } 686 }
313 public void ServeRequest(HTTPContext context) { 715 public void ServeRequest(HTTPContext context) {
314 KeyValuePair<string, IHTTPContentProvider> c = Prefixes.Find(delegate(KeyValuePair<string, IHTTPContentProvider> item) { return context.RequestPath.StartsWith(item.Key, PrefixComparison); }); 716 KeyValuePair<string, IHTTPContentProvider> c = Prefixes.Find(delegate(KeyValuePair<string, IHTTPContentProvider> item) { return context.RequestPath.StartsWith(item.Key, PrefixComparison); });
315 if (c.Value != null) { 717 if (c.Value != null) {
316 c.Value.ServeRequest(context); 718 c.Value.ServeRequest(context);
317 } else { 719 } else {
318 context.SendErrorAndClose(404); 720 context.SendErrorResponse(404);
319 } 721 }
320 } 722 }
321 } 723 }
322 public class HTTPStaticContent : IHTTPContentProvider { 724 public class HTTPStaticContent : IHTTPContentProvider {
323 public ArraySegment<Byte> ContentBuffer { get; set; } 725 public ArraySegment<Byte> ContentBuffer { get; set; }
336 public void SetContent(String content, Encoding encoding) { SetContent(encoding.GetBytes(content)); } 738 public void SetContent(String content, Encoding encoding) { SetContent(encoding.GetBytes(content)); }
337 public void SetContent(String content) { SetContent(content, Encoding.UTF8); } 739 public void SetContent(String content) { SetContent(content, Encoding.UTF8); }
338 public void ServeRequest(HTTPContext context) { 740 public void ServeRequest(HTTPContext context) {
339 ArraySegment<Byte> content = ContentBuffer; 741 ArraySegment<Byte> content = ContentBuffer;
340 if (content.Array == null) { 742 if (content.Array == null) {
341 context.SendErrorAndClose(404); 743 context.SendErrorResponse(404);
342 return; 744 return;
343 } 745 }
344 String contentType = ContentType; 746 String contentType = ContentType;
345 context.SendStatus(200); 747 context.SendStatus(200);
346 if (contentType != null) context.SendHeader("Content-Type", contentType); 748 if (contentType != null) context.SendHeader("Content-Type", contentType);
347 context.SendHeader("Content-Length", content.Count.ToString()); 749 context.WriteResponseData(content.Array, content.Offset, content.Count);
348 Stream response = context.GetResponseStream();
349 response.Write(content.Array, content.Offset, content.Count);
350 response.Close();
351 } 750 }
352 } 751 }
353 public class HTTPFileProvider : IHTTPContentProvider { 752 public class HTTPFileProvider : IHTTPContentProvider {
354 public String FileName { get; private set; } 753 public String FileName { get; private set; }
355 public String ContentType { get; private set; } 754 public String ContentType { get; private set; }
361 public void ServeRequest(HTTPContext context) { 760 public void ServeRequest(HTTPContext context) {
362 if (File.Exists(FileName)) { 761 if (File.Exists(FileName)) {
363 using (FileStream fs = File.OpenRead(FileName)) { 762 using (FileStream fs = File.OpenRead(FileName)) {
364 context.SendStatus(200); 763 context.SendStatus(200);
365 context.SendHeader("Content-Type", ContentType); 764 context.SendHeader("Content-Type", ContentType);
366 context.SendHeader("Content-Length", fs.Length.ToString());
367 long left = fs.Length; 765 long left = fs.Length;
368 Stream response = context.GetResponseStream(); 766 Stream response = context.OpenResponseStream(fs.Length);
369 byte[] buffer = new byte[1024 * 10]; 767 byte[] buffer = new byte[1024 * 10];
370 while (fs.CanRead) { 768 while (fs.CanRead) {
371 int len = fs.Read(buffer, 0, buffer.Length); 769 int len = fs.Read(buffer, 0, buffer.Length);
372 if (len <= 0) break; 770 if (len <= 0) break;
373 left -= len; 771 left -= len;
374 response.Write(buffer, 0, len); 772 response.Write(buffer, 0, len);
375 } 773 }
376 response.Close(); 774 response.Close();
377 } 775 }
378 } else { 776 } else {
379 context.SendErrorAndClose(404); 777 context.SendErrorResponse(404);
380 } 778 }
381 } 779 }
382 } 780 }
383 public class HTTPUnTarchiveProvider : IHTTPContentProvider { 781 public class HTTPUnTarchiveProvider : IHTTPContentProvider {
384 public String TarFileName { get; private set; } 782 public String TarFileName { get; private set; }
385 public HTTPUnTarchiveProvider(String tarFile) { 783 public HTTPUnTarchiveProvider(String tarFile) {
386 this.TarFileName = tarFile; 784 this.TarFileName = tarFile;
387 } 785 }
388 public void ServeRequest(HTTPContext context) { 786 public void ServeRequest(HTTPContext context) {
389 if (!File.Exists(TarFileName)) { 787 if (!File.Exists(TarFileName)) {
390 context.SendErrorAndClose(404); 788 context.SendErrorResponse(404);
391 return; 789 return;
392 } 790 }
393 String reqname1 = context.RequestPath; 791 String reqname1 = context.RequestPath;
394 if (reqname1.StartsWith("/")) reqname1 = reqname1.Substring(1); 792 if (reqname1.StartsWith("/")) reqname1 = reqname1.Substring(1);
395 String reqname2 = reqname1; 793 String reqname2 = reqname1;
397 reqname2 += "index.htm"; 795 reqname2 += "index.htm";
398 foreach (TarchiveEntry file in new TarchiveReader(TarFileName)) { 796 foreach (TarchiveEntry file in new TarchiveReader(TarFileName)) {
399 if (!file.IsFile) continue; 797 if (!file.IsFile) continue;
400 if (!reqname1.Equals(file.Name, StringComparison.OrdinalIgnoreCase) && !reqname2.Equals(file.Name, StringComparison.OrdinalIgnoreCase)) continue; 798 if (!reqname1.Equals(file.Name, StringComparison.OrdinalIgnoreCase) && !reqname2.Equals(file.Name, StringComparison.OrdinalIgnoreCase)) continue;
401 context.SendStatus(200); 799 context.SendStatus(200);
402 context.SendHeader("Content-Length", file.Size.ToString());
403 String ctype = null; 800 String ctype = null;
404 switch (Path.GetExtension(file.Name).ToLowerInvariant()) { 801 switch (Path.GetExtension(file.Name).ToLowerInvariant()) {
405 case ".txt": ctype = "text/plain"; break; 802 case ".txt": ctype = "text/plain"; break;
406 case ".htm": 803 case ".htm":
407 case ".html": ctype = "text/html"; break; 804 case ".html": ctype = "text/html"; break;
412 case ".jpeg": ctype = "image/jpeg"; break; 809 case ".jpeg": ctype = "image/jpeg"; break;
413 case ".gif": ctype = "image/gif"; break; 810 case ".gif": ctype = "image/gif"; break;
414 case ".ico": ctype = "image/x-icon"; break; 811 case ".ico": ctype = "image/x-icon"; break;
415 } 812 }
416 if (ctype != null) context.SendHeader("Content-Type", ctype); 813 if (ctype != null) context.SendHeader("Content-Type", ctype);
417 using (Stream response = context.GetResponseStream(), source = file.GetStream()) { 814 using (Stream response = context.OpenResponseStream(file.Size), source = file.GetStream()) {
418 byte[] buffer = new byte[Math.Min(source.Length, 1024 * 10)]; 815 byte[] buffer = new byte[Math.Min(source.Length, 1024 * 10)];
419 while (source.CanRead) { 816 while (source.CanRead) {
420 int len = source.Read(buffer, 0, buffer.Length); 817 int len = source.Read(buffer, 0, buffer.Length);
421 if (len <= 0) break; 818 if (len <= 0) break;
422 response.Write(buffer, 0, len); 819 response.Write(buffer, 0, len);
423 } 820 }
424 } 821 }
425 return; 822 return;
426 } 823 }
427 context.SendErrorAndClose(404); 824 context.SendErrorResponse(404);
428 } 825 }
429 } 826 }
430 } 827 }