2 // System.Web.HttpResponseStream.cs
6 // Miguel de Icaza (miguel@novell.com)
7 // Ben Maurer (bmaurer@ximian.com)
10 // Copyright (C) 2005-2009 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;
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 class HttpResponseStream : Stream
54 HttpResponse response;
57 byte [] chunk_buffer = new byte [24];
59 public HttpResponseStream (HttpResponse response)
61 this.response = response;
64 internal bool HaveFilter {
65 get { return filter != null; }
68 public Stream Filter {
71 filter = new OutputFilterStream (this);
78 unsafe sealed class BlockManager {
79 const int PreferredLength = 128 * 1024;
84 public BlockManager ()
89 get { return position; }
92 void EnsureCapacity (int capacity)
94 if (block_size >= capacity)
97 capacity += PreferredLength;
98 capacity = (capacity / PreferredLength) * PreferredLength;
101 ? (byte *) Marshal.AllocHGlobal (capacity)
102 : (byte *) Marshal.ReAllocHGlobal ((IntPtr) data, (IntPtr) capacity);
103 block_size = capacity;
106 public void Write (byte [] buffer, int offset, int count)
111 EnsureCapacity (position + count);
112 Marshal.Copy (buffer, offset, (IntPtr) (data + position), count);
116 public void Write (IntPtr ptr, int count)
121 EnsureCapacity (position + count);
122 byte *src = (byte *) ptr.ToPointer ();
124 byte *dest = (data + position);
125 for (int i = 0; i < count; i++)
128 memcpy (data + position, src, count);
133 public void Send (HttpWorkerRequest wr, int start, int end)
135 if (end - start <= 0)
138 wr.SendResponseFromMemory ((IntPtr) (data + start), end - start);
141 public void Send (Stream stream, int start, int end)
143 int len = end - start;
147 byte [] buffer = new byte [Math.Min (len, 32 * 1024)];
148 int size = buffer.Length;
150 Marshal.Copy ((IntPtr) (data + start), buffer, 0, size);
151 stream.Write (buffer, 0, size);
154 if (len > 0 && len < size)
159 public void Dispose ()
161 if ((IntPtr) data != IntPtr.Zero) {
162 Marshal.FreeHGlobal ((IntPtr) data);
163 data = (byte *) IntPtr.Zero;
168 abstract class Bucket {
171 public virtual void Dispose ()
175 public abstract void Send (HttpWorkerRequest wr);
176 public abstract void Send (Stream stream);
177 public abstract int Length { get; }
181 class ByteBucket : Bucket {
184 public BlockManager blocks;
185 public bool Expandable = true;
187 public ByteBucket () : this (null)
191 public ByteBucket (BlockManager blocks)
194 blocks = new BlockManager ();
196 this.blocks = blocks;
197 start = blocks.Position;
200 public override int Length {
201 get { return length; }
204 public unsafe int Write (byte [] buf, int offset, int count)
206 if (Expandable == false)
207 throw new Exception ("This should not happen.");
209 fixed (byte *p = &buf[0]) {
210 IntPtr p2 = new IntPtr (p + offset);
211 blocks.Write (p2, count);
218 public int Write (IntPtr ptr, int count)
220 if (Expandable == false)
221 throw new Exception ("This should not happen.");
223 blocks.Write (ptr, count);
228 public override void Dispose ()
233 public override void Send (HttpWorkerRequest wr)
238 blocks.Send (wr, start, length);
241 public override void Send (Stream stream)
246 blocks.Send (stream, start, length);
250 class BufferedFileBucket : Bucket {
255 public BufferedFileBucket (string f, long off, long len)
262 public override int Length {
263 get { return (int) length; }
266 public override void Send (HttpWorkerRequest wr)
268 wr.SendResponseFromFile (file, offset, length);
271 public override void Send (Stream stream)
273 using (FileStream fs = File.OpenRead (file)) {
274 byte [] buffer = new byte [Math.Min (fs.Length, 32*1024)];
276 long remain = fs.Length;
278 while (remain > 0 && (n = fs.Read (buffer, 0, (int) Math.Min (remain, 32*1024))) != 0){
280 stream.Write (buffer, 0, n);
285 public override string ToString ()
287 return "file " + file + " " + length.ToString () + " bytes from position " + offset.ToString ();
291 void AppendBucket (Bucket b)
293 if (first_bucket == null) {
294 cur_bucket = first_bucket = b;
303 // Nothing happens here, broken by requirement.
304 // See note at the start
306 public override void Flush ()
310 void SendChunkSize (long l, bool last)
317 string s = l.ToString ("x");
318 for (; i < s.Length; i++)
319 chunk_buffer [i] = (byte) s [i];
322 chunk_buffer [i++] = 13;
323 chunk_buffer [i++] = 10;
325 chunk_buffer [i++] = 13;
326 chunk_buffer [i++] = 10;
329 response.WorkerRequest.SendResponseFromMemory (chunk_buffer, i);
332 internal void Flush (HttpWorkerRequest wr, bool final_flush)
334 if (total == 0 && !final_flush)
337 if (response.use_chunked)
338 SendChunkSize (total, false);
340 for (Bucket b = first_bucket; b != null; b = b.Next) {
344 if (response.use_chunked) {
345 SendChunkSize (-1, false);
347 SendChunkSize (0, true);
350 wr.FlushResponse (final_flush);
355 internal int GetTotalLength ()
358 for (Bucket b = first_bucket; b != null; b = b.Next)
364 internal MemoryStream GetData ()
366 MemoryStream stream = new MemoryStream ();
367 for (Bucket b = first_bucket; b != null; b = b.Next)
372 public void WriteFile (string f, long offset, long length)
377 ByteBucket bb = cur_bucket as ByteBucket;
380 bb.Expandable = false;
381 bb = new ByteBucket (bb.blocks);
386 AppendBucket (new BufferedFileBucket (f, offset, length));
389 // Flush () is called from HttpResponse if needed (WriteFile/TransmitFile)
393 internal void ApplyFilter (bool close)
399 Bucket one = first_bucket;
400 first_bucket = null; // This will recreate new buckets for the filtered content
403 for (Bucket b = one; b != null; b = b.Next)
406 for (Bucket b = one; b != null; b = b.Next)
419 public void WritePtr (IntPtr ptr, int length)
424 bool buffering = response.BufferOutput;
427 // It does not matter whether we're in ApplyFilter or not
428 AppendBuffer (ptr, length);
429 } else if (filter == null || filtering) {
430 response.WriteHeaders (false);
431 HttpWorkerRequest wr = response.WorkerRequest;
432 // Direct write because not buffering
433 wr.SendResponseFromMemory (ptr, length);
434 wr.FlushResponse (false);
436 // Write to the filter, which will call us back, and then Flush
439 byte [] bytes = new byte [length];
440 Marshal.Copy (ptr, bytes, 0, length);
441 filter.Write (bytes, 0, length);
446 Flush (response.WorkerRequest, false);
451 public override void Write (byte [] buffer, int offset, int count)
453 bool buffering = response.BufferOutput;
455 throw new ArgumentNullException ("buffer");
457 int max_count = buffer.Length - offset;
458 if (offset < 0 || max_count <= 0)
459 throw new ArgumentOutOfRangeException ("offset");
461 throw new ArgumentOutOfRangeException ("count");
462 if (count > max_count)
466 // It does not matter whether we're in ApplyFilter or not
467 AppendBuffer (buffer, offset, count);
468 } else if (filter == null || filtering) {
469 response.WriteHeaders (false);
470 HttpWorkerRequest wr = response.WorkerRequest;
471 // Direct write because not buffering
473 wr.SendResponseFromMemory (buffer, count);
475 UnsafeWrite (wr, buffer, offset, count);
477 wr.FlushResponse (false);
479 // Write to the filter, which will call us back, and then Flush
482 filter.Write (buffer, offset, count);
486 Flush (response.WorkerRequest, false);
490 unsafe void UnsafeWrite (HttpWorkerRequest wr, byte [] buffer, int offset, int count)
492 fixed (byte *ptr = buffer) {
493 wr.SendResponseFromMemory ((IntPtr) (ptr + offset), count);
496 void AppendBuffer (byte [] buffer, int offset, int count)
498 if (!(cur_bucket is ByteBucket))
499 AppendBucket (new ByteBucket ());
502 ((ByteBucket) cur_bucket).Write (buffer, offset, count);
505 void AppendBuffer (IntPtr ptr, int count)
507 if (!(cur_bucket is ByteBucket))
508 AppendBucket (new ByteBucket ());
511 ((ByteBucket) cur_bucket).Write (ptr, count);
515 // This should not flush/close or anything else, its called
516 // just to free any memory we might have allocated (when we later
517 // implement something with unmanaged memory).
519 internal void ReleaseResources (bool close_filter)
521 if (close_filter && filter != null) {
526 for (Bucket b = first_bucket; b != null; b = b.Next)
536 // IMPORTANT: you must dispose *AFTER* using all the buckets Byte chunks might be
537 // split across two buckets if there is a file between the data.
539 ReleaseResources (false);
543 // Do not use directly. Use memcpy.
544 static unsafe void memcpy4 (byte *dest, byte *src, int size) {
545 /*while (size >= 32) {
546 // using long is better than int and slower than double
547 // FIXME: enable this only on correct alignment or on platforms
548 // that can tolerate unaligned reads/writes of doubles
549 ((double*)dest) [0] = ((double*)src) [0];
550 ((double*)dest) [1] = ((double*)src) [1];
551 ((double*)dest) [2] = ((double*)src) [2];
552 ((double*)dest) [3] = ((double*)src) [3];
558 ((int*)dest) [0] = ((int*)src) [0];
559 ((int*)dest) [1] = ((int*)src) [1];
560 ((int*)dest) [2] = ((int*)src) [2];
561 ((int*)dest) [3] = ((int*)src) [3];
567 ((int*)dest) [0] = ((int*)src) [0];
573 ((byte*)dest) [0] = ((byte*)src) [0];
580 // Do not use directly. Use memcpy.
581 static unsafe void memcpy2 (byte *dest, byte *src, int size) {
583 ((short*)dest) [0] = ((short*)src) [0];
584 ((short*)dest) [1] = ((short*)src) [1];
585 ((short*)dest) [2] = ((short*)src) [2];
586 ((short*)dest) [3] = ((short*)src) [3];
592 ((short*)dest) [0] = ((short*)src) [0];
598 ((byte*)dest) [0] = ((byte*)src) [0];
601 // Do not use directly. Use memcpy.
602 static unsafe void memcpy1 (byte *dest, byte *src, int size) {
604 ((byte*)dest) [0] = ((byte*)src) [0];
605 ((byte*)dest) [1] = ((byte*)src) [1];
606 ((byte*)dest) [2] = ((byte*)src) [2];
607 ((byte*)dest) [3] = ((byte*)src) [3];
608 ((byte*)dest) [4] = ((byte*)src) [4];
609 ((byte*)dest) [5] = ((byte*)src) [5];
610 ((byte*)dest) [6] = ((byte*)src) [6];
611 ((byte*)dest) [7] = ((byte*)src) [7];
617 ((byte*)dest) [0] = ((byte*)src) [0];
618 ((byte*)dest) [1] = ((byte*)src) [1];
624 ((byte*)dest) [0] = ((byte*)src) [0];
627 static unsafe void memcpy (byte *dest, byte *src, int size) {
628 // FIXME: if pointers are not aligned, try to align them
629 // so a faster routine can be used. Handle the case where
630 // the pointers can't be reduced to have the same alignment
631 // (just ignore the issue on x86?)
632 if ((((int)dest | (int)src) & 3) != 0) {
633 if (((int)dest & 1) != 0 && ((int)src & 1) != 0 && size >= 1) {
639 if (((int)dest & 2) != 0 && ((int)src & 2) != 0 && size >= 2) {
640 ((short*)dest) [0] = ((short*)src) [0];
645 if ((((int)dest | (int)src) & 1) != 0) {
646 memcpy1 (dest, src, size);
649 if ((((int)dest | (int)src) & 2) != 0) {
650 memcpy2 (dest, src, size);
654 memcpy4 (dest, src, size);
657 public override bool CanRead {
663 public override bool CanSeek {
669 public override bool CanWrite {
675 const string notsupported = "HttpResponseStream is a forward, write-only stream";
677 public override long Length {
679 throw new NotSupportedException (notsupported);
683 public override long Position {
685 throw new NotSupportedException (notsupported);
688 throw new NotSupportedException (notsupported);
692 public override long Seek (long offset, SeekOrigin origin)
694 throw new NotSupportedException (notsupported);
697 public override void SetLength (long value)
699 throw new NotSupportedException (notsupported);
702 public override int Read (byte [] buffer, int offset, int count)
704 throw new NotSupportedException (notsupported);