Mercurial > hg > ucis.core
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 } |