PageRenderTime 64ms CodeModel.GetById 21ms RepoModel.GetById 1ms app.codeStats 0ms

/src/Manos/Manos.Http/HttpResponseStream.cs

https://github.com/davidalpert/manos
C# | 393 lines | 266 code | 82 blank | 45 comment | 51 complexity | 3bf50976be5922295150584d88568f28 MD5 | raw file
  1. //
  2. // Copyright (C) 2010 Jackson Harper (jackson@manosdemono.com)
  3. //
  4. // Permission is hereby granted, free of charge, to any person obtaining
  5. // a copy of this software and associated documentation files (the
  6. // "Software"), to deal in the Software without restriction, including
  7. // without limitation the rights to use, copy, modify, merge, publish,
  8. // distribute, sublicense, and/or sell copies of the Software, and to
  9. // permit persons to whom the Software is furnished to do so, subject to
  10. // the following conditions:
  11. //
  12. // The above copyright notice and this permission notice shall be
  13. // included in all copies or substantial portions of the Software.
  14. //
  15. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  16. // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  17. // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  18. // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
  19. // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
  20. // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
  21. // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  22. //
  23. //
  24. using System;
  25. using System.IO;
  26. using System.Text;
  27. using System.Linq;
  28. using System.Collections;
  29. using System.Collections.Generic;
  30. namespace Manos.Http
  31. {
  32. //
  33. // TODO: As a first pass in an attempt to keep this class as simple as
  34. // possible we'll allow a little ineffeciency. In the case of seeking
  35. // back into the middle of a segment and then doing a write, we'll just
  36. // truncate the segment and insert the next segment, instead of trying
  37. // to conserve space.
  38. //
  39. // This isn't neccasarily bad, I just need to research the buffer sizes
  40. // a little more and figure out which is the most effecient way of
  41. // doing things.
  42. //
  43. // One fear here is StreamWriters that write single bytes at a time. This
  44. // would create a long list of single byte arrays. An easy fix for this
  45. // would be to start using the unused space at the end of segments and
  46. // start making a minimum segment size, so in the case of a single byte
  47. // write, we would create a 24 byte buffer and set the count to 1, the
  48. // next write would use the remaining 23 bytes.
  49. //
  50. public class HttpResponseStream : Stream
  51. {
  52. private long segment_offset;
  53. private int current_segment;
  54. public static readonly int MIN_BUFFER_SIZE = 24;
  55. private List<ArraySegment<byte>> segments = new List<ArraySegment<byte>> (10);
  56. public HttpResponseStream ()
  57. {
  58. }
  59. public override bool CanRead {
  60. get { return true; }
  61. }
  62. public override bool CanSeek {
  63. get { return true; }
  64. }
  65. public override bool CanWrite {
  66. get { return true; }
  67. }
  68. public override long Length {
  69. get {
  70. if (segments.Count == 0)
  71. return 0;
  72. long total = segments.Sum (s => s.Count);
  73. return total;
  74. }
  75. }
  76. private ArraySegment<byte> CurrentSegment {
  77. get { return segments [current_segment]; }
  78. }
  79. private bool AtEnd {
  80. get {
  81. if (segments.Count == 0)
  82. return true;
  83. return current_segment == segments.Count - 1 && segment_offset == CurrentSegment.Count;
  84. }
  85. }
  86. public override long Position {
  87. get {
  88. long pos = 0;
  89. for (int i = 0; i < current_segment; i++) {
  90. pos += segments [i].Count;
  91. }
  92. pos += segment_offset;
  93. return pos;
  94. }
  95. set {
  96. Seek (value, SeekOrigin.Begin);
  97. }
  98. }
  99. public override void Flush ()
  100. {
  101. // NOOP
  102. }
  103. public override int Read (byte[] buffer, int offset, int count)
  104. {
  105. if (buffer == null)
  106. throw new ArgumentNullException ("buffer");
  107. if (offset < 0)
  108. throw new ArgumentOutOfRangeException ("offset");
  109. if (count < 0)
  110. throw new ArgumentOutOfRangeException ("count");
  111. if (offset + count > buffer.Length)
  112. throw new ArgumentException ("The sum of offset and count is larger than the buffer length.");
  113. long read_count = 0;
  114. while (current_segment < segments.Count) {
  115. if (segment_offset == CurrentSegment.Count) {
  116. segment_offset = 0;
  117. ++current_segment;
  118. continue;
  119. }
  120. long amount = Math.Min (count - read_count, CurrentSegment.Count - segment_offset);
  121. Array.Copy (CurrentSegment.Array, CurrentSegment.Offset + segment_offset, buffer, offset + read_count, amount);
  122. read_count += amount;
  123. segment_offset += amount;
  124. if (read_count == count)
  125. break;
  126. }
  127. return (int) read_count;
  128. }
  129. public override long Seek (long offset, SeekOrigin origin)
  130. {
  131. switch (origin) {
  132. case SeekOrigin.Begin:
  133. return SeekFromBegin (offset);
  134. case SeekOrigin.Current:
  135. return SeekFromCurrent (offset);
  136. case SeekOrigin.End:
  137. return SeekFromEnd (offset);
  138. }
  139. throw new ArgumentException ("Invalid SeekOrigin.");
  140. }
  141. private long SeekFromBegin (long offset)
  142. {
  143. if (offset < 0)
  144. throw new ArgumentException ("Can not seek past beginning of stream.");
  145. if (offset == 0) {
  146. current_segment = 0;
  147. segment_offset = 0;
  148. return 0;
  149. }
  150. if (segments.Count < 1)
  151. throw new ArgumentException ("Can not seek on empty stream.");
  152. return SeekForward (0, 0, offset);
  153. }
  154. private long SeekForward (int segment, long pos, long offset)
  155. {
  156. bool found = false;
  157. for ( ; segment < segments.Count; segment++) {
  158. if (pos + segments [segment].Count > offset) {
  159. found = true;
  160. break;
  161. }
  162. pos += segments [segment].Count;
  163. }
  164. if (!found)
  165. throw new ArgumentException ("Can not seek past the end of stream.");
  166. current_segment = segment;
  167. segment_offset = offset - pos;
  168. return Position;
  169. }
  170. private long SeekBackwards (int segment, long pos, long offset)
  171. {
  172. long offset_amount = Math.Abs (offset);
  173. bool found = false;
  174. for ( ; segment >= 0; segment--) {
  175. if (pos > offset_amount){
  176. found = true;
  177. break;
  178. }
  179. pos += segments [segment].Count;
  180. }
  181. if (!found)
  182. throw new ArgumentException ("Can not seek past beginning of stream.");
  183. current_segment = segment;
  184. segment_offset = pos - offset_amount;
  185. return Position;
  186. }
  187. private long SeekFromEnd (long offset)
  188. {
  189. return SeekBackwards (current_segment, segment_offset, offset);
  190. }
  191. private long SeekFromCurrent (long offset)
  192. {
  193. if (offset == 0)
  194. return Position;
  195. if (offset > 0)
  196. return SeekForward (current_segment, segment_offset, offset);
  197. return SeekBackwards (current_segment, segment_offset, offset);
  198. }
  199. public override void SetLength (long value)
  200. {
  201. long len = 0;
  202. int segment = 0;
  203. for ( ; segment < segments.Count; segment++) {
  204. if (len + segments [segment].Count < value) {
  205. len += segments [segment].Count;
  206. continue;
  207. }
  208. len += segments [segment].Count;
  209. break;
  210. }
  211. if (len == value)
  212. return;
  213. if (len > value) {
  214. current_segment = segment;
  215. segment_offset = len - value;
  216. ArraySegment<byte> old = segments [segment];
  217. segments [segment] = new ArraySegment<byte> (old.Array, old.Offset, (int) segment_offset);
  218. while (segments.Count > segment + 1)
  219. segments.RemoveAt (segment + 1);
  220. return;
  221. }
  222. byte [] filler = new byte [value - len];
  223. segments.Add (new ArraySegment<byte> (filler, 0, filler.Length));
  224. }
  225. public void Insert (byte [] buffer, int offset, int count)
  226. {
  227. if (AtEnd) {
  228. Write (buffer, offset, count);
  229. return;
  230. }
  231. var segment = new ArraySegment<byte> (buffer, offset, count);
  232. segments.Insert (current_segment, segment);
  233. segment_offset = count;
  234. }
  235. public List<ArraySegment<byte>> GetBuffers ()
  236. {
  237. return segments;
  238. }
  239. public override void Write (byte[] buffer, int offset, int count)
  240. {
  241. byte [] copy = new byte [buffer.Length];
  242. Array.Copy (buffer, offset, copy, offset, count);
  243. buffer = copy;
  244. if (AtEnd) {
  245. if (buffer.Length < MIN_BUFFER_SIZE) {
  246. byte [] bigger = new byte [MIN_BUFFER_SIZE];
  247. Array.Copy (buffer, offset, bigger, 0, count);
  248. buffer = bigger;
  249. }
  250. segments.Add (new ArraySegment<byte> (buffer, offset, count));
  251. current_segment = segments.Count - 1;
  252. segment_offset = count;
  253. return;
  254. }
  255. if (CurrentSegment.Count - segment_offset > count) {
  256. // There is room to stick this in the current segment.
  257. Array.Copy (buffer, offset, CurrentSegment.Array, segment_offset, count);
  258. return;
  259. }
  260. int index = current_segment;
  261. RemoveBytes (count);
  262. segments.Insert (index + 1, new ArraySegment<byte> (buffer, offset, count));
  263. current_segment = index + 1;
  264. segment_offset = count;
  265. }
  266. private void CutSegment (int segment, long position)
  267. {
  268. var s = segments [segment];
  269. if (position == s.Count) {
  270. // At the end of the segment so nothing needs to be done.
  271. return;
  272. }
  273. var data = new byte [s.Count - position];
  274. Array.Copy (s.Array, s.Offset + position, data, 0, s.Count - position);
  275. var after = new ArraySegment<byte> (data, 0, data.Length);
  276. segments.Insert (segment + 1, after);
  277. segments [segment] = new ArraySegment<byte> (s.Array, s.Offset, (int) position);
  278. }
  279. private void RemoveBytes (int amount)
  280. {
  281. if (CurrentSegment.Offset != segment_offset) {
  282. CutSegment (current_segment, segment_offset);
  283. ++current_segment;
  284. segment_offset = 0;
  285. }
  286. int segment = current_segment;
  287. int offset = (int) segment_offset;
  288. int removed = 0;
  289. int start_segment = current_segment;
  290. while (segment <= segments.Count - 1) {
  291. int segment_len = segments [segment].Count - offset;
  292. if (removed + segment_len == amount) {
  293. break;
  294. }
  295. if (removed + segment_len > amount) {
  296. // we need to end in this segment, truncate it by the proper amount
  297. int len = segment_len - (amount - removed);
  298. segments [segment] = new ArraySegment<byte> (segments [segment].Array, segments [segment].Offset, len);
  299. break;
  300. }
  301. removed += segment_len;
  302. ++segment;
  303. offset = 0;
  304. }
  305. int num_segments = segment - start_segment;
  306. for (int i = 0; i < num_segments; i++) {
  307. segments.RemoveAt (start_segment);
  308. }
  309. }
  310. }
  311. }