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 HttpResponse response;
56 byte [] chunk_buffer = new byte [24];
58 public HttpResponseStream (HttpResponse response)
60 this.response = response;
63 internal bool HaveFilter {
64 get { return filter != null; }
67 public Stream Filter {
70 filter = new OutputFilterStream (this);
80 const int PreferredLength = 16 * 1024;
81 static readonly byte[] EmptyBuffer = new byte[0];
83 byte[] buffer = EmptyBuffer;
86 public BlockManager () {
90 get { return position; }
93 void EnsureCapacity (int capacity) {
94 if (buffer.Length >= capacity)
97 capacity += PreferredLength;
98 capacity = (capacity / PreferredLength) * PreferredLength;
99 byte[] temp = new byte[capacity];
100 Array.Copy(buffer, 0, temp, 0, buffer.Length);
104 public void Write (byte [] buffer, int offset, int count) {
108 EnsureCapacity (position + count);
109 Array.Copy(buffer, offset, this.buffer, position, count);
113 public void Send (HttpWorkerRequest wr, int start, int end) {
114 int length = end - start;
118 if (length > buffer.Length - start)
119 length = buffer.Length - start;
122 byte[] temp = new byte[length];
123 Array.Copy(buffer, start, temp, 0, length);
126 wr.SendResponseFromMemory(buffer, length);
129 public void Send (Stream stream, int start, int end) {
130 int length = end - start;
134 if (length > buffer.Length - start)
135 length = buffer.Length - start;
137 stream.Write(buffer, start, length);
140 public void Dispose () {
146 unsafe sealed class BlockManager {
147 const int PreferredLength = 128 * 1024;
152 public BlockManager ()
156 public int Position {
157 get { return position; }
160 void EnsureCapacity (int capacity)
162 if (block_size >= capacity)
165 capacity += PreferredLength;
166 capacity = (capacity / PreferredLength) * PreferredLength;
169 ? (byte *) Marshal.AllocHGlobal (capacity)
170 : (byte *) Marshal.ReAllocHGlobal ((IntPtr) data, (IntPtr) capacity);
171 block_size = capacity;
174 public void Write (byte [] buffer, int offset, int count)
179 EnsureCapacity (position + count);
180 Marshal.Copy (buffer, offset, (IntPtr) (data + position), count);
184 public void Send (HttpWorkerRequest wr, int start, int end)
186 if (end - start <= 0)
189 wr.SendResponseFromMemory ((IntPtr) (data + start), end - start);
192 public void Send (Stream stream, int start, int end)
194 int len = end - start;
198 byte [] buffer = new byte [Math.Min (len, 32 * 1024)];
199 int size = buffer.Length;
201 Marshal.Copy ((IntPtr) (data + start), buffer, 0, size);
202 stream.Write (buffer, 0, size);
205 if (len > 0 && len < size)
210 public void Dispose ()
212 if ((IntPtr) data != IntPtr.Zero) {
213 Marshal.FreeHGlobal ((IntPtr) data);
214 data = (byte *) IntPtr.Zero;
220 abstract class Bucket {
223 public virtual void Dispose ()
227 public abstract void Send (HttpWorkerRequest wr);
228 public abstract void Send (Stream stream);
229 public abstract int Length { get; }
235 class ByteBucket : Bucket {
238 public BlockManager blocks;
239 public bool Expandable = true;
241 public ByteBucket () : this (null)
245 public ByteBucket (BlockManager blocks)
248 blocks = new BlockManager ();
250 this.blocks = blocks;
251 start = blocks.Position;
254 public override int Length {
255 get { return length; }
258 public int Write (byte [] buf, int offset, int count)
260 if (Expandable == false)
261 throw new Exception ("This should not happen.");
263 blocks.Write (buf, offset, count);
268 public override void Dispose ()
273 public override void Send (HttpWorkerRequest wr)
278 blocks.Send (wr, start, length);
281 public override void Send (Stream stream)
286 blocks.Send (stream, start, length);
290 class BufferedFileBucket : Bucket {
295 public BufferedFileBucket (string f, long off, long len)
302 public override int Length {
303 get { return (int) length; }
306 public override void Send (HttpWorkerRequest wr)
308 wr.SendResponseFromFile (file, offset, length);
311 public override void Send (Stream stream)
313 using (FileStream fs = File.OpenRead (file)) {
314 byte [] buffer = new byte [Math.Min (fs.Length, 32*1024)];
316 long remain = fs.Length;
318 while (remain > 0 && (n = fs.Read (buffer, 0, (int) Math.Min (remain, 32*1024))) != 0){
320 stream.Write (buffer, 0, n);
325 public override string ToString ()
327 return String.Format ("file {0} {1} bytes from position {2}", file, length, offset);
331 void AppendBucket (Bucket b)
333 if (first_bucket == null) {
334 cur_bucket = first_bucket = b;
343 // Nothing happens here, broken by requirement.
344 // See note at the start
346 public override void Flush ()
350 void SendChunkSize (long l, bool last)
357 string s = String.Format ("{0:x}", l);
358 for (; i < s.Length; i++)
359 chunk_buffer [i] = (byte) s [i];
362 chunk_buffer [i++] = 13;
363 chunk_buffer [i++] = 10;
365 chunk_buffer [i++] = 13;
366 chunk_buffer [i++] = 10;
369 response.WorkerRequest.SendResponseFromMemory (chunk_buffer, i);
372 internal void Flush (HttpWorkerRequest wr, bool final_flush)
374 if (total == 0 && !final_flush)
377 if (response.use_chunked)
378 SendChunkSize (total, false);
380 for (Bucket b = first_bucket; b != null; b = b.Next) {
384 if (response.use_chunked) {
385 SendChunkSize (-1, false);
387 SendChunkSize (0, true);
390 wr.FlushResponse (final_flush);
395 internal int GetTotalLength ()
398 for (Bucket b = first_bucket; b != null; b = b.Next)
404 internal MemoryStream GetData ()
406 MemoryStream stream = new MemoryStream ();
407 for (Bucket b = first_bucket; b != null; b = b.Next)
412 public void WriteFile (string f, long offset, long length)
417 ByteBucket bb = cur_bucket as ByteBucket;
420 bb.Expandable = false;
421 bb = new ByteBucket (bb.blocks);
426 AppendBucket (new BufferedFileBucket (f, offset, length));
429 // Flush () is called from HttpResponse if needed (WriteFile/TransmitFile)
433 internal void ApplyFilter (bool close)
439 Bucket one = first_bucket;
440 first_bucket = null; // This will recreate new buckets for the filtered content
443 for (Bucket b = one; b != null; b = b.Next)
446 for (Bucket b = one; b != null; b = b.Next)
459 public override void Write (byte [] buffer, int offset, int count)
461 bool buffering = response.Buffer;
464 // It does not matter whether we're in ApplyFilter or not
465 AppendBuffer (buffer, offset, count);
466 } else if (filter == null || filtering) {
467 response.WriteHeaders (false);
468 HttpWorkerRequest wr = response.WorkerRequest;
469 // Direct write because not buffering
471 wr.SendResponseFromMemory (buffer, count);
473 UnsafeWrite (wr, buffer, offset, count);
475 wr.FlushResponse (false);
477 // Write to the filter, which will call us back, and then Flush
480 filter.Write (buffer, offset, count);
484 Flush (response.WorkerRequest, false);
489 void UnsafeWrite (HttpWorkerRequest wr, byte [] buffer, int offset, int count)
494 byte[] copy = new byte[count];
495 Array.Copy(buffer, offset, copy, 0, count);
496 wr.SendResponseFromMemory (copy, count);
499 unsafe void UnsafeWrite (HttpWorkerRequest wr, byte [] buffer, int offset, int count)
501 fixed (byte *ptr = buffer) {
502 wr.SendResponseFromMemory ((IntPtr) (ptr + offset), count);
506 void AppendBuffer (byte [] buffer, int offset, int count)
508 if (!(cur_bucket is ByteBucket))
509 AppendBucket (new ByteBucket ());
512 ((ByteBucket) cur_bucket).Write (buffer, offset, count);
516 // This should not flush/close or anything else, its called
517 // just to free any memory we might have allocated (when we later
518 // implement something with unmanaged memory).
520 internal void ReleaseResources (bool close_filter)
522 if (close_filter && filter != null) {
527 for (Bucket b = first_bucket; b != null; b = b.Next)
537 // IMPORTANT: you must dispose *AFTER* using all the buckets Byte chunks might be
538 // split across two buckets if there is a file between the data.
540 ReleaseResources (false);
544 public override bool CanRead {
550 public override bool CanSeek {
555 public override bool CanWrite {
561 const string notsupported = "HttpResponseStream is a forward, write-only stream";
563 public override long Length {
565 throw new InvalidOperationException (notsupported);
569 public override long Position {
571 throw new InvalidOperationException (notsupported);
574 throw new InvalidOperationException (notsupported);
578 public override long Seek (long offset, SeekOrigin origin)
580 throw new InvalidOperationException (notsupported);
583 public override void SetLength (long value)
585 throw new InvalidOperationException (notsupported);
588 public override int Read (byte [] buffer, int offset, int count)
590 throw new InvalidOperationException (notsupported);