Skip to content
This repository was archived by the owner on Dec 14, 2018. It is now read-only.

Commit 40794fc

Browse files
committed
Custom stream writer which avoids writing the BOM and does not flush or close the stream.
1 parent d6012d4 commit 40794fc

File tree

7 files changed

+642
-128
lines changed

7 files changed

+642
-128
lines changed

src/Microsoft.AspNet.Mvc.Core/ActionResults/ViewExecutor.cs

Lines changed: 3 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -64,98 +64,10 @@ public static async Task ExecuteAsync([NotNull] IView view,
6464

6565
response.ContentType = contentTypeHeader.ToString();
6666

67-
var wrappedStream = new StreamWrapper(response.Body);
68-
69-
using (var writer = new StreamWriter(wrappedStream, encoding, BufferSize, leaveOpen: true))
70-
{
71-
try
72-
{
73-
var viewContext = new ViewContext(actionContext, view, viewData, tempData, writer);
74-
await view.RenderAsync(viewContext);
75-
}
76-
catch
77-
{
78-
// Need to prevent writes/flushes on dispose because the StreamWriter will flush even if
79-
// nothing got written. This leads to a response going out on the wire prematurely in case an
80-
// exception is being thrown inside the try catch block.
81-
wrappedStream.BlockWrites = true;
82-
throw;
83-
}
84-
}
85-
}
86-
87-
private class StreamWrapper : Stream
88-
{
89-
private readonly Stream _wrappedStream;
90-
91-
public StreamWrapper(Stream stream)
92-
{
93-
_wrappedStream = stream;
94-
}
95-
96-
public bool BlockWrites { get; set; }
97-
98-
public override bool CanRead
99-
{
100-
get { return _wrappedStream.CanRead; }
101-
}
102-
103-
public override bool CanSeek
104-
{
105-
get { return _wrappedStream.CanSeek; }
106-
}
107-
108-
public override bool CanWrite
109-
{
110-
get { return _wrappedStream.CanWrite; }
111-
}
112-
113-
public override void Flush()
114-
{
115-
if (!BlockWrites)
116-
{
117-
_wrappedStream.Flush();
118-
}
119-
}
120-
121-
public override long Length
122-
{
123-
get { return _wrappedStream.Length; }
124-
}
125-
126-
public override long Position
67+
using (var writer = new HttpResponseStreamWriter(response.Body, encoding))
12768
{
128-
get
129-
{
130-
return _wrappedStream.Position;
131-
}
132-
set
133-
{
134-
_wrappedStream.Position = value;
135-
}
136-
}
137-
138-
public override int Read(byte[] buffer, int offset, int count)
139-
{
140-
return _wrappedStream.Read(buffer, offset, count);
141-
}
142-
143-
public override long Seek(long offset, SeekOrigin origin)
144-
{
145-
return Seek(offset, origin);
146-
}
147-
148-
public override void SetLength(long value)
149-
{
150-
SetLength(value);
151-
}
152-
153-
public override void Write(byte[] buffer, int offset, int count)
154-
{
155-
if (!BlockWrites)
156-
{
157-
_wrappedStream.Write(buffer, offset, count);
158-
}
69+
var viewContext = new ViewContext(actionContext, view, viewData, tempData, writer);
70+
await view.RenderAsync(viewContext);
15971
}
16072
}
16173
}

src/Microsoft.AspNet.Mvc.Core/Formatters/JsonOutputFormatter.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,8 +70,7 @@ public override Task WriteResponseBodyAsync(OutputFormatterContext context)
7070
var response = context.ActionContext.HttpContext.Response;
7171
var selectedEncoding = context.SelectedEncoding;
7272

73-
using (var nonDisposableStream = new NonDisposableStream(response.Body))
74-
using (var writer = new StreamWriter(nonDisposableStream, selectedEncoding, 1024, leaveOpen: true))
73+
using (var writer = new HttpResponseStreamWriter(response.Body, selectedEncoding))
7574
{
7675
WriteObject(writer, context.Object);
7776
}
Lines changed: 235 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,235 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System;
5+
using System.IO;
6+
using System.Text;
7+
using System.Threading.Tasks;
8+
using Microsoft.Framework.Internal;
9+
10+
namespace Microsoft.AspNet.Mvc
11+
{
12+
/// <summary>
13+
/// Writes to the <see cref="Stream"/> using the supplied <see cref="Encoding"/>.
14+
/// It does not write the BOM and also does not close the stream.
15+
/// </summary>
16+
public class HttpResponseStreamWriter : TextWriter
17+
{
18+
private const int DefaultBufferSize = 1024;
19+
private readonly Stream _stream;
20+
private Encoder _encoder;
21+
private byte[] _byteBuffer;
22+
private char[] _charBuffer;
23+
private int _charBufferSize;
24+
private int _charBufferCount;
25+
26+
public HttpResponseStreamWriter(Stream stream, Encoding encoding)
27+
: this(stream, encoding, DefaultBufferSize)
28+
{
29+
}
30+
31+
public HttpResponseStreamWriter([NotNull] Stream stream, [NotNull] Encoding encoding, int bufferSize)
32+
{
33+
_stream = stream;
34+
Encoding = encoding;
35+
_encoder = encoding.GetEncoder();
36+
_charBufferSize = bufferSize;
37+
_charBuffer = new char[bufferSize];
38+
_byteBuffer = new byte[encoding.GetMaxByteCount(bufferSize)];
39+
}
40+
41+
public override Encoding Encoding { get; }
42+
43+
public override void Write(char value)
44+
{
45+
if (_charBufferCount == _charBufferSize)
46+
{
47+
FlushInternal();
48+
}
49+
50+
_charBuffer[_charBufferCount] = value;
51+
_charBufferCount++;
52+
}
53+
54+
public override void Write(char[] values, int index, int count)
55+
{
56+
if (values == null)
57+
{
58+
return;
59+
}
60+
61+
while (count > 0)
62+
{
63+
if (_charBufferCount == _charBufferSize)
64+
{
65+
FlushInternal();
66+
}
67+
68+
CopyToCharBuffer(values, ref index, ref count);
69+
}
70+
}
71+
72+
public override void Write(string value)
73+
{
74+
if (value == null)
75+
{
76+
return;
77+
}
78+
79+
var count = value.Length;
80+
var index = 0;
81+
while (count > 0)
82+
{
83+
if (_charBufferCount == _charBufferSize)
84+
{
85+
FlushInternal();
86+
}
87+
88+
CopyToCharBuffer(value, ref index, ref count);
89+
}
90+
}
91+
92+
public override async Task WriteAsync(char value)
93+
{
94+
if (_charBufferCount == _charBufferSize)
95+
{
96+
await FlushInternalAsync();
97+
}
98+
99+
_charBuffer[_charBufferCount] = value;
100+
_charBufferCount++;
101+
}
102+
103+
public override async Task WriteAsync(char[] values, int index, int count)
104+
{
105+
if (values == null)
106+
{
107+
return;
108+
}
109+
110+
while (count > 0)
111+
{
112+
if (_charBufferCount == _charBufferSize)
113+
{
114+
await FlushInternalAsync();
115+
}
116+
117+
CopyToCharBuffer(values, ref index, ref count);
118+
}
119+
}
120+
121+
public override async Task WriteAsync(string value)
122+
{
123+
if (value == null)
124+
{
125+
return;
126+
}
127+
128+
var count = value.Length;
129+
var index = 0;
130+
while (count > 0)
131+
{
132+
if (_charBufferCount == _charBufferSize)
133+
{
134+
await FlushInternalAsync();
135+
}
136+
137+
CopyToCharBuffer(value, ref index, ref count);
138+
}
139+
}
140+
141+
// We want to flush the stream when Flush/FlushAsync is explicitly
142+
// called by the user (example: from a Razor view).
143+
144+
public override void Flush()
145+
{
146+
FlushInternal(true, true);
147+
}
148+
149+
public override async Task FlushAsync()
150+
{
151+
await FlushInternalAsync(true, true);
152+
}
153+
154+
// Do not flush the stream on Dispose, as this will cause response to be
155+
// sent in chunked encoding in case of Helios.
156+
protected override void Dispose(bool disposing)
157+
{
158+
FlushInternal(flushStream: false, flushEncoder: true);
159+
}
160+
161+
private void FlushInternal(bool flushStream = false, bool flushEncoder = false)
162+
{
163+
if (_charBufferCount == 0)
164+
{
165+
return;
166+
}
167+
168+
var count = _encoder.GetBytes(_charBuffer, 0, _charBufferCount, _byteBuffer, 0, flushEncoder);
169+
if (count > 0)
170+
{
171+
_stream.Write(_byteBuffer, 0, count);
172+
}
173+
174+
_charBufferCount = 0;
175+
176+
if (flushStream)
177+
{
178+
_stream.Flush();
179+
}
180+
}
181+
182+
private async Task FlushInternalAsync(bool flushStream = false, bool flushEncoder = false)
183+
{
184+
if (_charBufferCount == 0)
185+
{
186+
return;
187+
}
188+
189+
var count = _encoder.GetBytes(_charBuffer, 0, _charBufferCount, _byteBuffer, 0, flushEncoder);
190+
if (count > 0)
191+
{
192+
await _stream.WriteAsync(_byteBuffer, 0, count);
193+
}
194+
195+
_charBufferCount = 0;
196+
197+
if (flushStream)
198+
{
199+
await _stream.FlushAsync();
200+
}
201+
}
202+
203+
private void CopyToCharBuffer(string value, ref int index, ref int count)
204+
{
205+
var remaining = Math.Min(_charBufferSize - _charBufferCount, count);
206+
207+
value.CopyTo(
208+
sourceIndex: index,
209+
destination: _charBuffer,
210+
destinationIndex: _charBufferCount,
211+
count: remaining);
212+
213+
_charBufferCount += remaining;
214+
index += remaining;
215+
count -= remaining;
216+
}
217+
218+
private void CopyToCharBuffer(char[] values, ref int index, ref int count)
219+
{
220+
var remaining = Math.Min(_charBufferSize - _charBufferCount, count);
221+
222+
Buffer.BlockCopy(
223+
src: values,
224+
srcOffset: index * sizeof(char),
225+
dst: _charBuffer,
226+
dstOffset: _charBufferCount * sizeof(char),
227+
count: remaining * sizeof(char));
228+
229+
_charBufferCount += remaining;
230+
index += remaining;
231+
count -= remaining;
232+
}
233+
}
234+
}
235+

0 commit comments

Comments
 (0)