Skip to content
This repository was archived by the owner on Jan 23, 2023. It is now read-only.

Commit 28f8b9b

Browse files
committed
Fix ContentLength64, SendChunk, and KeepAlive in managed HttpListener
Move Windows implementation to shared, and rewrite managed implementation to use it. Remove setting of KeepAlive header, and fix up handling of content length with regards to chunked, Close(..., willBlock:false), etc.
1 parent d372670 commit 28f8b9b

File tree

9 files changed

+169
-168
lines changed

9 files changed

+169
-168
lines changed

src/System.Net.HttpListener/src/System.Net.HttpListener.csproj

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@
4747
<Reference Include="System.Threading.Timer" />
4848
</ItemGroup>
4949
<ItemGroup>
50+
<Compile Include="System\Net\BoundaryType.cs" />
51+
<Compile Include="System\Net\EntitySendFormat.cs" />
5052
<Compile Include="System\Net\HttpListenerPrefixCollection.cs" />
5153
<Compile Include="System\Net\HttpRequestStream.cs" />
5254
<Compile Include="System\Net\AuthenticationTypes.cs" />
@@ -108,14 +110,12 @@
108110
<Compile Include="System\Net\Windows\HttpListenerRequest.Windows.cs" />
109111
<Compile Include="System\Net\Windows\HttpListenerResponse.Windows.cs" />
110112
<Compile Include="System\Net\Windows\HttpListenerTimeoutManager.Windows.cs" />
111-
<Compile Include="System\Net\Windows\BoundaryType.cs" />
112113
<Compile Include="System\Net\Windows\ContextFlags.cs" />
113114
<Compile Include="System\Net\Windows\HttpRequestQueueV2Handle.cs" />
114115
<Compile Include="System\Net\Windows\HttpServerSessionHandle.cs" />
115116
<Compile Include="System\Net\Windows\HttpListenerRequestContext.cs" />
116117
<Compile Include="System\Net\Windows\ListenerClientCertAsyncResult.Windows.cs" />
117118
<Compile Include="System\Net\Windows\AsyncRequestContext.cs" />
118-
<Compile Include="System\Net\Windows\EntitySendFormat.cs" />
119119
<Compile Include="System\Net\Windows\RequestContextBase.cs" />
120120
<Compile Include="System\Net\Windows\SyncRequestContext.cs" />
121121
<Compile Include="System\Net\Windows\ListenerAsyncResult.Windows.cs" />

src/System.Net.HttpListener/src/System/Net/Windows/BoundaryType.cs renamed to src/System.Net.HttpListener/src/System/Net/BoundaryType.cs

File renamed without changes.

src/System.Net.HttpListener/src/System/Net/Windows/EntitySendFormat.cs renamed to src/System.Net.HttpListener/src/System/Net/EntitySendFormat.cs

File renamed without changes.

src/System.Net.HttpListener/src/System/Net/HttpListenerResponse.cs

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,9 @@ namespace System.Net
1010
{
1111
public sealed unsafe partial class HttpListenerResponse : IDisposable
1212
{
13+
private BoundaryType _boundaryType = BoundaryType.None;
1314
private CookieCollection _cookies;
15+
private HttpListenerContext _httpContext;
1416
private bool _keepAlive = true;
1517
private HttpResponseStream _responseStream;
1618
private string _statusDescription;
@@ -48,6 +50,69 @@ public string ContentType
4850
}
4951
}
5052

53+
private HttpListenerContext HttpListenerContext => _httpContext;
54+
55+
private HttpListenerRequest HttpListenerRequest => HttpListenerContext.Request;
56+
57+
internal EntitySendFormat EntitySendFormat
58+
{
59+
get => (EntitySendFormat)_boundaryType;
60+
set
61+
{
62+
CheckDisposed();
63+
CheckSentHeaders();
64+
if (value == EntitySendFormat.Chunked && HttpListenerRequest.ProtocolVersion.Minor == 0)
65+
{
66+
throw new ProtocolViolationException(SR.net_nochunkuploadonhttp10);
67+
}
68+
_boundaryType = (BoundaryType)value;
69+
if (value != EntitySendFormat.ContentLength)
70+
{
71+
_contentLength = -1;
72+
}
73+
}
74+
}
75+
76+
public bool SendChunked
77+
{
78+
get => EntitySendFormat == EntitySendFormat.Chunked;
79+
set => EntitySendFormat = value ? EntitySendFormat.Chunked : EntitySendFormat.ContentLength;
80+
}
81+
82+
// We MUST NOT send message-body when we send responses with these Status codes
83+
private static readonly int[] s_noResponseBody = { 100, 101, 204, 205, 304 };
84+
85+
private static bool CanSendResponseBody(int responseCode)
86+
{
87+
for (int i = 0; i < s_noResponseBody.Length; i++)
88+
{
89+
if (responseCode == s_noResponseBody[i])
90+
{
91+
return false;
92+
}
93+
}
94+
return true;
95+
}
96+
97+
public long ContentLength64
98+
{
99+
get => _contentLength;
100+
set
101+
{
102+
CheckDisposed();
103+
CheckSentHeaders();
104+
if (value >= 0)
105+
{
106+
_contentLength = value;
107+
_boundaryType = BoundaryType.ContentLength;
108+
}
109+
else
110+
{
111+
throw new ArgumentOutOfRangeException(nameof(value), SR.net_clsmall);
112+
}
113+
}
114+
}
115+
51116
public CookieCollection Cookies
52117
{
53118
get => _cookies ?? (_cookies = new CookieCollection());

src/System.Net.HttpListener/src/System/Net/Managed/HttpHeaderStrings.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ namespace System.Net
77
internal class HttpHeaderStrings
88
{
99
internal const string Charset = "charset=";
10-
internal const string NetCoreServerName = ".NETCore";
10+
internal const string NetCoreServerName = "Microsoft-NetCore/2.0";
1111
internal const string Close = "close";
1212
internal const string Chunked = "chunked";
1313
internal const string KeepAlive = "keep-alive";

src/System.Net.HttpListener/src/System/Net/Managed/HttpListenerResponse.Managed.cs

Lines changed: 82 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -31,49 +31,29 @@
3131
using System.Globalization;
3232
using System.IO;
3333
using System.Text;
34-
using System.Threading.Tasks;
3534

3635
namespace System.Net
3736
{
3837
public sealed partial class HttpListenerResponse : IDisposable
3938
{
4039
private long _contentLength;
41-
private bool _clSet;
4240
private Version _version = HttpVersion.Version11;
4341
private int _statusCode = 200;
44-
private bool _chunked;
45-
private HttpListenerContext _context;
4642
internal object _headersLock = new object();
4743
private bool _forceCloseChunked;
4844

4945
internal HttpListenerResponse(HttpListenerContext context)
5046
{
51-
_context = context;
47+
_httpContext = context;
5248
}
5349

5450
internal bool ForceCloseChunked => _forceCloseChunked;
5551

56-
public long ContentLength64
57-
{
58-
get => _contentLength;
59-
set
60-
{
61-
CheckDisposed();
62-
CheckSentHeaders();
63-
64-
if (value < 0)
65-
throw new ArgumentOutOfRangeException(nameof(value), SR.net_clsmall);
66-
67-
_clSet = true;
68-
_contentLength = value;
69-
}
70-
}
71-
7252
private void EnsureResponseStream()
7353
{
7454
if (_responseStream == null)
7555
{
76-
_responseStream = _context.Connection.GetResponseStream();
56+
_responseStream = _httpContext.Connection.GetResponseStream();
7757
}
7858
}
7959

@@ -95,18 +75,6 @@ public Version ProtocolVersion
9575
}
9676
}
9777

98-
public bool SendChunked
99-
{
100-
get => _chunked;
101-
set
102-
{
103-
CheckDisposed();
104-
CheckSentHeaders();
105-
106-
_chunked = value;
107-
}
108-
}
109-
11078
public int StatusCode
11179
{
11280
get => _statusCode;
@@ -142,19 +110,49 @@ public void Abort()
142110
private void Close(bool force)
143111
{
144112
Disposed = true;
145-
_context.Connection.Close(force);
113+
_httpContext.Connection.Close(force);
146114
}
147115

148116
public void Close(byte[] responseEntity, bool willBlock)
149117
{
150118
CheckDisposed();
151119

152120
if (responseEntity == null)
121+
{
153122
throw new ArgumentNullException(nameof(responseEntity));
123+
}
154124

155-
ContentLength64 = responseEntity.Length;
156-
OutputStream.Write(responseEntity, 0, (int)_contentLength);
157-
Close(false);
125+
if (_boundaryType != BoundaryType.Chunked)
126+
{
127+
ContentLength64 = responseEntity.Length;
128+
}
129+
130+
if (willBlock)
131+
{
132+
try
133+
{
134+
OutputStream.Write(responseEntity, 0, responseEntity.Length);
135+
}
136+
finally
137+
{
138+
Close(false);
139+
}
140+
}
141+
else
142+
{
143+
OutputStream.BeginWrite(responseEntity, 0, responseEntity.Length, iar =>
144+
{
145+
var thisRef = (HttpListenerResponse)iar.AsyncState;
146+
try
147+
{
148+
thisRef.OutputStream.EndWrite(iar);
149+
}
150+
finally
151+
{
152+
thisRef.Close(false);
153+
}
154+
}, this);
155+
}
158156
}
159157

160158
public void CopyFrom(HttpListenerResponse templateResponse)
@@ -191,26 +189,49 @@ internal void SendHeaders(bool closing, MemoryStream ms, bool isWebSocketHandsha
191189
if (!isWebSocketHandshake)
192190
{
193191
if (_webHeaders[HttpKnownHeaderNames.Server] == null)
192+
{
194193
_webHeaders.Set(HttpKnownHeaderNames.Server, HttpHeaderStrings.NetCoreServerName);
195-
CultureInfo inv = CultureInfo.InvariantCulture;
194+
}
195+
196196
if (_webHeaders[HttpKnownHeaderNames.Date] == null)
197-
_webHeaders.Set(HttpKnownHeaderNames.Date, DateTime.UtcNow.ToString("r", inv));
197+
{
198+
_webHeaders.Set(HttpKnownHeaderNames.Date, DateTime.UtcNow.ToString("r", CultureInfo.InvariantCulture));
199+
}
198200

199-
if (!_chunked)
201+
if (_boundaryType == BoundaryType.None)
200202
{
201-
if (!_clSet && closing)
203+
if (HttpListenerRequest.ProtocolVersion <= HttpVersion.Version10)
202204
{
203-
_clSet = true;
204-
_contentLength = 0;
205+
_keepAlive = false;
206+
}
207+
else
208+
{
209+
_boundaryType = BoundaryType.Chunked;
205210
}
206211

207-
if (_clSet)
208-
_webHeaders.Set(HttpKnownHeaderNames.ContentLength, _contentLength.ToString(inv));
212+
if (CanSendResponseBody(_httpContext.Response.StatusCode))
213+
{
214+
_contentLength = -1;
215+
}
216+
else
217+
{
218+
_boundaryType = BoundaryType.ContentLength;
219+
_contentLength = 0;
220+
}
209221
}
210222

211-
Version v = _context.Request.ProtocolVersion;
212-
if (!_clSet && !_chunked && v >= HttpVersion.Version11)
213-
_chunked = true;
223+
if (_boundaryType != BoundaryType.Chunked)
224+
{
225+
if (_boundaryType != BoundaryType.ContentLength && closing)
226+
{
227+
_contentLength = CanSendResponseBody(_httpContext.Response.StatusCode) ? -1 : 0;
228+
}
229+
230+
if (_boundaryType == BoundaryType.ContentLength)
231+
{
232+
_webHeaders.Set(HttpKnownHeaderNames.ContentLength, _contentLength.ToString("D", CultureInfo.InvariantCulture));
233+
}
234+
}
214235

215236
/* Apache forces closing the connection for these status codes:
216237
* HttpStatusCode.BadRequest 400
@@ -226,8 +247,10 @@ internal void SendHeaders(bool closing, MemoryStream ms, bool isWebSocketHandsha
226247
|| _statusCode == (int)HttpStatusCode.RequestUriTooLong || _statusCode == (int)HttpStatusCode.InternalServerError
227248
|| _statusCode == (int)HttpStatusCode.ServiceUnavailable);
228249

229-
if (conn_close == false)
230-
conn_close = !_context.Request.KeepAlive;
250+
if (!conn_close)
251+
{
252+
conn_close = !_httpContext.Request.KeepAlive;
253+
}
231254

232255
// They sent both KeepAlive: true and Connection: close
233256
if (!_keepAlive || conn_close)
@@ -236,10 +259,12 @@ internal void SendHeaders(bool closing, MemoryStream ms, bool isWebSocketHandsha
236259
conn_close = true;
237260
}
238261

239-
if (_chunked)
262+
if (SendChunked)
263+
{
240264
_webHeaders.Set(HttpKnownHeaderNames.TransferEncoding, HttpHeaderStrings.Chunked);
265+
}
241266

242-
int reuses = _context.Connection.Reuses;
267+
int reuses = _httpContext.Connection.Reuses;
243268
if (reuses >= 100)
244269
{
245270
_forceCloseChunked = true;
@@ -250,17 +275,17 @@ internal void SendHeaders(bool closing, MemoryStream ms, bool isWebSocketHandsha
250275
}
251276
}
252277

253-
if (!conn_close)
278+
if (!conn_close && _httpContext.Request.ProtocolVersion <= HttpVersion.Version10)
254279
{
255-
_webHeaders.Set(HttpKnownHeaderNames.KeepAlive, String.Format("timeout=15,max={0}", 100 - reuses));
256-
if (_context.Request.ProtocolVersion <= HttpVersion.Version10)
257-
_webHeaders.Set(HttpKnownHeaderNames.Connection, HttpHeaderStrings.KeepAlive);
280+
_webHeaders.Set(HttpKnownHeaderNames.Connection, HttpHeaderStrings.KeepAlive);
258281
}
259282

260283
if (_cookies != null)
261284
{
262285
foreach (Cookie cookie in _cookies)
286+
{
263287
_webHeaders.Set(HttpKnownHeaderNames.SetCookie, CookieToClientString(cookie));
288+
}
264289
}
265290
}
266291

0 commit comments

Comments
 (0)