forked from aspnet/AspNetWebStack
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathSeekableBufferedRequestStream.cs
More file actions
173 lines (144 loc) · 5.45 KB
/
Copy pathSeekableBufferedRequestStream.cs
File metadata and controls
173 lines (144 loc) · 5.45 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
namespace System.Web.Http.WebHost
{
internal class SeekableBufferedRequestStream : NonOwnedStream
{
private const int ReadBufferSize = 1024;
private readonly HttpRequestBase _request;
private bool _isReadToEndComplete;
public SeekableBufferedRequestStream(HttpRequestBase request)
{
if (request == null)
{
throw new ArgumentNullException("request");
}
_request = request;
InnerStream = request.GetBufferedInputStream();
}
public override bool CanSeek
{
get
{
return !IsDisposed;
}
}
public override long Position
{
get
{
ThrowIfDisposed();
return InnerStream.Position;
}
set
{
ThrowIfDisposed();
Seek(value, SeekOrigin.Begin);
}
}
public override int EndRead(IAsyncResult asyncResult)
{
ThrowIfDisposed();
int bytesRead = InnerStream.EndRead(asyncResult);
if (bytesRead == 0 && !_isReadToEndComplete)
{
SwapToSeekableStream();
}
return bytesRead;
}
public override int Read(byte[] buffer, int offset, int count)
{
ThrowIfDisposed();
int bytesRead = InnerStream.Read(buffer, offset, count);
if (bytesRead == 0 && !_isReadToEndComplete)
{
SwapToSeekableStream();
}
return bytesRead;
}
public async override Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{
ThrowIfDisposed();
int bytesRead = await InnerStream.ReadAsync(buffer, offset, count, cancellationToken);
if (bytesRead == 0 && !_isReadToEndComplete)
{
SwapToSeekableStream();
}
return bytesRead;
}
public override int ReadByte()
{
ThrowIfDisposed();
int result = InnerStream.ReadByte();
if (result == -1 && !_isReadToEndComplete)
{
SwapToSeekableStream();
}
return result;
}
public override long Seek(long offset, SeekOrigin origin)
{
ThrowIfDisposed();
long currentPosition = InnerStream.Position;
long? newPosition = null;
switch (origin)
{
case SeekOrigin.Begin:
newPosition = offset;
break;
case SeekOrigin.Current:
newPosition = currentPosition + offset;
break;
case SeekOrigin.End:
// We have to check Length here because we might not know the length in some scenarios.
// If we don't know, then we just do the safe thing and force a read to end.
if (Length >= 0)
{
newPosition = Length + offset;
}
break;
default:
throw new InvalidEnumArgumentException("origin", (int)origin, typeof(SeekOrigin));
}
if (newPosition == currentPosition)
{
// This is a no-op, we want to short circuit because we do significant work on a seek.
return currentPosition;
}
if (!_isReadToEndComplete)
{
// The current stream is the one returned from GetBufferedInputStream(), and it's not
// seekable in the web host case.
//
// We need to read the non-seekable stream to the end, which will populate the seekable stream
// that's provided by .InputStream. This is only done for the side-effect, and we just ignore the
// data, it's already being buffered for us.
//
// This is done synchronously, because we need to block the calling thread so that the result of
// Seek can be returned.
byte[] buffer = new byte[ReadBufferSize];
while (InnerStream.Read(buffer, 0, buffer.Length) > 0)
{
}
SwapToSeekableStream();
}
return InnerStream.Seek(offset, origin);
}
private void SwapToSeekableStream()
{
// At this point we've actually read the non-seekable stream to the end, and we're about to swap streams
// and toggle the value of _isReadToEndComplete. Reading the non-seekable stream to the end will populate
// InnerStream with the buffered data, so we can use it for all future operations.
Debug.Assert(!_isReadToEndComplete);
Stream seekableStream = _request.InputStream;
seekableStream.Position = InnerStream.Position;
InnerStream = seekableStream;
_isReadToEndComplete = true;
}
}
}