2 // System.Web.HttpResponseStream.cs
6 // Miguel de Icaza (miguel@novell.com)
7 // Ben Maurer (bmaurer@ximian.com)
10 // Copyright (C) 2005 Novell, Inc (http://www.novell.com)
12 // Permission is hereby granted, free of charge, to any person obtaining
13 // a copy of this software and associated documentation files (the
14 // "Software"), to deal in the Software without restriction, including
15 // without limitation the rights to use, copy, modify, merge, publish,
16 // distribute, sublicense, and/or sell copies of the Software, and to
17 // permit persons to whom the Software is furnished to do so, subject to
18 // the following conditions:
20 // The above copyright notice and this permission notice shall be
21 // included in all copies or substantial portions of the Software.
23 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
24 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
25 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
26 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
27 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
28 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
29 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
35 using System.Globalization;
36 using System.Runtime.InteropServices;
38 namespace System.Web {
41 // HttpResponseStream implements the "OutputStream" from HttpResponse
43 // The MS implementation is broken in that it does not hook up this
44 // to HttpResponse, so calling "Flush" on Response.OutputStream does not
45 // flush the contents and produce the headers.
47 // You must call HttpResponse.Flush which does the actual header generation
48 // and actual data flushing
50 internal class HttpResponseStream : Stream {
53 Bucket split_after_file;
54 HttpResponse response;
57 byte [] chunk_buffer = new byte [24];
59 public HttpResponseStream (HttpResponse response)
61 this.response = response;
64 public Stream Filter {
67 filter = new OutputFilterStream (this);
79 public const int Length = 32 * 1024;
81 public Chunk Next, Prev;
85 data = new byte[Length];
89 public void Dispose ()
93 public static Chunk Unlink (ref Chunk head, Chunk b)
100 b.Next.Prev = b.Prev;
102 b.Next = b.Prev = null;
106 public static Chunk Link (Chunk head, Chunk b)
114 public static void Copy(byte[] buff, int offset, Chunk c, int pos, int len)
116 Array.Copy(buff, offset, c.data, pos, len);
119 public static void Copy(Chunk c, int pos, byte[] buff, int offset, int len)
121 Array.Copy(c.data, pos, buff, offset, len);
125 sealed class BufferManager {
129 // TODO: if cb > chunk size, try to get a larger chunk
130 public static Chunk GetChunk (int cb)
134 empty = new Chunk ();
137 filled = Chunk.Link (filled, Chunk.Unlink (ref empty, c));
141 public static void DisposeChunk (Chunk c)
143 empty = Chunk.Link (empty, Chunk.Unlink (ref filled, c));
146 public static void DisposeEmptyChunks ()
148 for (Chunk c = empty; c != null; c = c.Next)
154 public static void PrintState ()
156 Console.WriteLine ("Filled blocks:");
157 for (Chunk c = filled; c != null; c = c.Next)
158 Console.WriteLine ("\t{0}", c);
159 Console.WriteLine ("Empty blocks:");
160 for (Chunk c = empty; c != null; c = c.Next)
161 Console.WriteLine ("\t{0}", c);
170 const int PreferredLength = 128 * 1024;
171 public const int ChunkSize = 32 * 1024;
172 const int PreferredChunks = PreferredLength / ChunkSize;
178 public Block Next, Prev;
181 public Block () : this (PreferredLength)
185 public Block (int initial_size)
187 if (initial_size <= PreferredLength) {
188 actual_length = PreferredLength;
189 nchunks = PreferredChunks;
191 actual_length = initial_size;
194 data = (byte*) Marshal.AllocHGlobal (actual_length);
197 public void Dispose ()
199 Marshal.FreeHGlobal ((IntPtr) data);
202 public Chunk GetChunk (int size)
204 Chunk c = new Chunk (size);
205 for (int i = 0; i < nchunks; i ++) {
206 if ((taken & (1 << i)) == 0) {
208 c.block_area = 1 << i;
209 c.data = data + i * ChunkSize;
210 taken |= c.block_area;
215 throw new Exception ("Internal error: shouldn't get here");
218 public static Block Unlink (ref Block list, Block b)
221 b.Prev.Next = b.Next;
223 b.Next.Prev = b.Prev;
227 b.Next = b.Prev = null;
231 public static void Link (ref Block head, Block b)
239 public void Return (Chunk c)
241 taken &= ~c.block_area;
244 public bool IsEmpty {
252 return taken == (1 << nchunks) - 1;
256 public override string ToString ()
259 for (int i = 0; i < nchunks; i ++) {
260 if ((taken & (1 << i)) == 0)
266 return String.Format ("0x{0:x} {1}", (IntPtr) data, bitmap);
271 unsafe struct Chunk {
274 public int block_area;
277 public Chunk (int size)
285 public static void Copy(byte[] buff, int offset, Chunk c, int pos, int len)
287 Marshal.Copy (buff, offset, (IntPtr) (c.data + pos), len);
290 public static void Copy(Chunk c, int pos, byte[] buff, int offset, int len)
292 Marshal.Copy ((IntPtr) (c.data + pos), buff, offset, len);
296 static Block part_filled;
299 // TODO: if cb > chunk size, try to get a larger chunk
300 public static Chunk GetChunk (int cb)
302 if (cb < Block.ChunkSize)
303 cb = Block.ChunkSize;
306 if (part_filled != null) {
307 c = part_filled.GetChunk (cb);
308 if (part_filled.IsFull)
309 Block.Link (ref filled, Block.Unlink (ref part_filled, part_filled));
315 Block.Link (ref empty, new Block (cb));
317 c = empty.GetChunk (cb);
318 if (empty.IsFull) // account for the case where we have 1 chunk/block
319 Block.Link (ref filled, Block.Unlink (ref empty, empty));
321 Block.Link (ref part_filled, Block.Unlink (ref empty, empty));
325 public static void DisposeChunk (Chunk c)
328 bool was_full = b.IsFull;
333 Block.Link (ref part_filled, Block.Unlink (ref filled, b));
335 Block.Link (ref empty, Block.Unlink (ref part_filled, b));
338 public static void DisposeEmptyChunks ()
340 for (Block b = empty; b != null; b = b.Next)
346 public static void PrintState ()
348 Console.WriteLine ("Filled blocks:");
349 for (Block b = filled; b != null; b = b.Next)
350 Console.WriteLine ("\t{0}", b);
351 Console.WriteLine ("Part Filled blocks:");
352 for (Block b = part_filled; b != null; b = b.Next)
353 Console.WriteLine ("\t{0}", b);
354 Console.WriteLine ("Empty blocks:");
355 for (Block b = empty; b != null; b = b.Next)
356 Console.WriteLine ("\t{0}", b);
363 abstract class Bucket {
366 public virtual void Dispose ()
370 public abstract void Send (HttpWorkerRequest wr);
371 public abstract void Send (Stream stream);
372 public abstract int Length { get; }
378 class ByteBucket : Bucket {
387 public ByteBucket (int cb)
389 c = Chunk.GetChunk (cb);
394 public override int Length {
395 get { return pos - start; }
398 public int Write (byte [] buf, int offset, int count)
400 int copy = Math.Min (rem, count);
404 Chunk.Copy (buf, offset, c, pos, copy);
412 public ByteBucket SplitOff ()
414 // Don't give people a really short bucket.
418 ByteBucket b = new ByteBucket ();
421 b.start = b.pos = pos;
427 public override void Dispose ()
430 Chunk.DisposeChunk (c);
433 public override void Send (HttpWorkerRequest wr)
435 int len = pos - start;
437 for (int i = 0; i < len; i++)
438 Console.Write ("[{0}:{1}]", start [i], start [i]);
439 Console.WriteLine ("Sending {0} bytes", len);
440 Console.WriteLine (Environment.StackTrace);
445 wr.SendResponseFromMemory (c.data, len);
448 byte[] buf = new byte[len];
449 Chunk.Copy(c, start, buf, 0, len);
452 wr.SendResponseFromMemory ((IntPtr) (c.data + start), len);
456 public override void Send (Stream stream)
458 int len = (int) (pos - start);
459 byte [] copy = new byte [len];
460 Chunk.Copy (c, start, copy, 0, len);
461 stream.Write (copy, 0, len);
465 class BufferedFileBucket : Bucket {
470 public BufferedFileBucket (string f, long off, long len)
477 public override int Length {
478 get { return (int) length; }
481 public override void Send (HttpWorkerRequest wr)
483 wr.SendResponseFromFile (file, offset, length);
486 public override void Send (Stream stream)
488 using (FileStream fs = File.OpenRead (file)) {
489 byte [] buffer = new byte [Math.Min (fs.Length, 32*1024)];
491 long remain = fs.Length;
493 while (remain > 0 && (n = fs.Read (buffer, 0, (int) Math.Min (remain, 32*1024))) != 0){
495 stream.Write (buffer, 0, n);
500 public override string ToString ()
502 return String.Format ("file {0} {1} bytes from position {2}", file, length, offset);
506 void AppendBucket (Bucket b)
508 if (first_bucket == null) {
509 cur_bucket = first_bucket = b;
518 // Nothing happens here, broken by requirement.
519 // See note at the start
521 public override void Flush ()
525 void SendChunkSize (long l, bool last)
532 string s = String.Format ("{0:x}", l);
533 for (; i < s.Length; i++)
534 chunk_buffer [i] = (byte) s [i];
537 chunk_buffer [i++] = 13;
538 chunk_buffer [i++] = 10;
540 chunk_buffer [i++] = 13;
541 chunk_buffer [i++] = 10;
544 response.WorkerRequest.SendResponseFromMemory (chunk_buffer, i);
547 internal void Flush (HttpWorkerRequest wr, bool final_flush)
549 if (total == 0 && !final_flush)
552 if (response.use_chunked)
553 SendChunkSize (total, false);
555 for (Bucket b = first_bucket; b != null; b = b.Next)
558 if (response.use_chunked) {
559 SendChunkSize (-1, false);
561 SendChunkSize (0, true);
564 wr.FlushResponse (final_flush);
569 internal int GetTotalLength ()
572 for (Bucket b = first_bucket; b != null; b = b.Next)
578 internal MemoryStream GetData ()
580 MemoryStream stream = new MemoryStream ();
581 for (Bucket b = first_bucket; b != null; b = b.Next)
586 public void WriteFile (string f, long offset, long length)
588 ByteBucket bb = cur_bucket as ByteBucket;
591 split_after_file = bb.SplitOff ();
595 AppendBucket (new BufferedFileBucket (f, offset, length));
596 // Flush () is called from HttpResponse if needed (WriteFile/TransmitFile)
600 internal void ApplyFilter (bool close)
606 Bucket one = first_bucket;
607 first_bucket = null; // This will recreate new buckets for the filtered content
610 for (Bucket b = one; b != null; b = b.Next)
613 for (Bucket b = one; b != null; b = b.Next)
625 public override void Write (byte [] buffer, int offset, int count)
627 bool buffering = response.Buffer;
630 // It does not matter whether we're in ApplyFilter or not
631 AppendBuffer (buffer, offset, count);
632 } else if (filter == null || filtering) {
633 response.WriteHeaders (false);
634 HttpWorkerRequest wr = response.WorkerRequest;
635 // Direct write because not buffering
637 wr.SendResponseFromMemory (buffer, count);
639 UnsafeWrite (wr, buffer, offset, count);
641 wr.FlushResponse (false);
643 // Write to the filter, which will call us back, and then Flush
646 filter.Write (buffer, offset, count);
650 Flush (response.WorkerRequest, false);
654 unsafe void UnsafeWrite (HttpWorkerRequest wr, byte [] buffer, int offset, int count)
656 fixed (byte *ptr = buffer) {
657 wr.SendResponseFromMemory ((IntPtr) (ptr + offset), count);
661 void AppendBuffer (byte [] buffer, int offset, int count)
663 if (cur_bucket == null)
664 AppendBucket (new ByteBucket (count));
668 if (cur_bucket is ByteBucket) {
669 int n = ((ByteBucket) cur_bucket).Write (buffer, offset, count);
674 if (split_after_file != null) {
675 AppendBucket (split_after_file);
676 split_after_file = null;
681 AppendBucket (new ByteBucket (count));
686 // This should not flush/close or anything else, its called
687 // just to free any memory we might have allocated (when we later
688 // implement something with unmanaged memory).
690 internal void ReleaseResources (bool close_filter)
692 if (close_filter && filter != null) {
697 for (Bucket b = first_bucket; b != null; b = b.Next)
702 split_after_file = null;
708 // IMPORTANT: you must dispose *AFTER* using all the buckets Byte chunks might be
709 // split across two buckets if there is a file between the data.
711 ReleaseResources (false);
715 public override bool CanRead {
721 public override bool CanSeek {
726 public override bool CanWrite {
732 const string notsupported = "HttpResponseStream is a forward, write-only stream";
734 public override long Length {
736 throw new InvalidOperationException (notsupported);
740 public override long Position {
742 throw new InvalidOperationException (notsupported);
745 throw new InvalidOperationException (notsupported);
749 public override long Seek (long offset, SeekOrigin origin)
751 throw new InvalidOperationException (notsupported);
754 public override void SetLength (long value)
756 throw new InvalidOperationException (notsupported);
759 public override int Read (byte [] buffer, int offset, int count)
761 throw new InvalidOperationException (notsupported);