This commit is contained in:
Chris R 2015-06-04 11:36:13 -07:00
Родитель 0acb8f3ed4
Коммит 3c044fb92e
19 изменённых файлов: 945 добавлений и 827 удалений

Просмотреть файл

@ -39,6 +39,7 @@ namespace Microsoft.AspNet.Server.WebListener
IHttpSendFileFeature,
ITlsConnectionFeature,
ITlsTokenBindingFeature,
IHttpBufferingFeature,
IHttpRequestLifetimeFeature,
IHttpWebSocketFeature,
IHttpAuthenticationFeature,
@ -97,6 +98,7 @@ namespace Microsoft.AspNet.Server.WebListener
_features.Add(typeof(IHttpConnectionFeature), this);
_features.Add(typeof(IHttpResponseFeature), this);
_features.Add(typeof(IHttpSendFileFeature), this);
_features.Add(typeof(IHttpBufferingFeature), this);
_features.Add(typeof(IHttpRequestLifetimeFeature), this);
_features.Add(typeof(IHttpAuthenticationFeature), this);
_features.Add(typeof(IRequestIdentifierFeature), this);
@ -328,6 +330,16 @@ namespace Microsoft.AspNet.Server.WebListener
return Request.GetReferredTokenBindingId();
}
void IHttpBufferingFeature.DisableRequestBuffering()
{
// There is no request buffering.
}
void IHttpBufferingFeature.DisableResponseBuffering()
{
Response.ShouldBuffer = false;
}
Stream IHttpResponseFeature.Body
{
get
@ -356,7 +368,7 @@ namespace Microsoft.AspNet.Server.WebListener
bool IHttpResponseFeature.HeadersSent
{
get { return Response.HeadersSent; }
get { return Response.HasStarted; }
}
void IHttpResponseFeature.OnSendingHeaders(Action<object> callback, object state)

Просмотреть файл

@ -167,13 +167,14 @@ namespace Microsoft.AspNet.Server.WebListener
catch (Exception ex)
{
LogHelper.LogException(_logger, "ProcessRequestAsync", ex);
if (requestContext.Response.HeadersSent)
if (requestContext.Response.HasStartedSending)
{
requestContext.Abort();
}
else
{
// We haven't sent a response yet, try to send a 500 Internal Server Error
requestContext.Response.Reset();
SetFatalResponse(requestContext, 500);
}
}
@ -195,8 +196,6 @@ namespace Microsoft.AspNet.Server.WebListener
private static void SetFatalResponse(RequestContext context, int status)
{
context.Response.StatusCode = status;
context.Response.ReasonPhrase = string.Empty;
context.Response.Headers.Clear();
context.Response.ContentLength = 0;
context.Dispose();
}

Просмотреть файл

@ -21,13 +21,18 @@
// </copyright>
// -----------------------------------------------------------------------
using System;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
namespace Microsoft.Net.Http.Server
{
internal static class Helpers
{
internal static readonly byte[] ChunkTerminator = new byte[] { (byte)'0', (byte)'\r', (byte)'\n', (byte)'\r', (byte)'\n' };
internal static readonly byte[] CRLF = new byte[] { (byte)'\r', (byte)'\n' };
internal static Task CompletedTask()
{
return Task.FromResult<object>(null);
@ -49,5 +54,94 @@ namespace Microsoft.Net.Http.Server
{
return task.ConfigureAwait(continueOnCapturedContext: false);
}
internal static IAsyncResult ToIAsyncResult(this Task task, AsyncCallback callback, object state)
{
var tcs = new TaskCompletionSource<int>(state);
task.ContinueWith(t =>
{
if (t.IsFaulted)
{
tcs.TrySetException(t.Exception.InnerExceptions);
}
else if (t.IsCanceled)
{
tcs.TrySetCanceled();
}
else
{
tcs.TrySetResult(0);
}
if (callback != null)
{
callback(tcs.Task);
}
}, CancellationToken.None, TaskContinuationOptions.None, TaskScheduler.Default);
return tcs.Task;
}
/// <summary>
/// A private utility routine to convert an integer to a chunk header,
/// which is an ASCII hex number followed by a CRLF.The header is returned
/// as a byte array.
/// Generates a right-aligned hex string and returns the start offset.
/// </summary>
/// <param name="size">Chunk size to be encoded</param>
/// <param name="offset">Out parameter where we store offset into buffer.</param>
/// <returns>A byte array with the header in int.</returns>
internal static ArraySegment<byte> GetChunkHeader(int size)
{
uint mask = 0xf0000000;
byte[] header = new byte[10];
int i;
int offset = -1;
// Loop through the size, looking at each nibble. If it's not 0
// convert it to hex. Save the index of the first non-zero
// byte.
for (i = 0; i < 8; i++, size <<= 4)
{
// offset == -1 means that we haven't found a non-zero nibble
// yet. If we haven't found one, and the current one is zero,
// don't do anything.
if (offset == -1)
{
if ((size & mask) == 0)
{
continue;
}
}
// Either we have a non-zero nibble or we're no longer skipping
// leading zeros. Convert this nibble to ASCII and save it.
uint temp = (uint)size >> 28;
if (temp < 10)
{
header[i] = (byte)(temp + '0');
}
else
{
header[i] = (byte)((temp - 10) + 'A');
}
// If we haven't found a non-zero nibble yet, we've found one
// now, so remember that.
if (offset == -1)
{
offset = i;
}
}
header[8] = (byte)'\r';
header[9] = (byte)'\n';
return new ArraySegment<byte>(header, offset, header.Length - offset);
}
}
}

Просмотреть файл

@ -0,0 +1,50 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Microsoft.Net.Http.Server
{
internal class BufferBuilder
{
private List<ArraySegment<byte>> _buffers = new List<ArraySegment<byte>>();
internal IEnumerable<ArraySegment<byte>> Buffers
{
get { return _buffers; }
}
internal int BufferCount
{
get { return _buffers.Count; }
}
internal int TotalBytes { get; private set; }
internal void Add(ArraySegment<byte> data)
{
_buffers.Add(data);
TotalBytes += data.Count;
}
public void CopyAndAdd(ArraySegment<byte> data)
{
if (data.Count > 0)
{
var temp = new byte[data.Count];
Buffer.BlockCopy(data.Array, data.Offset, temp, 0, data.Count);
_buffers.Add(new ArraySegment<byte>(temp));
TotalBytes += data.Count;
}
}
public void Clear()
{
_buffers.Clear();
TotalBytes = 0;
}
}
}

Просмотреть файл

@ -21,15 +21,15 @@ namespace Microsoft.Net.Http.Server
private IDictionary<string, string[]> Store { get; set; }
// Readonly after the response has been sent.
internal bool Sent { get; set; }
// Readonly after the response has been started.
public bool IsReadOnly { get; internal set; }
public string this[string key]
{
get { return Get(key); }
set
{
ThrowIfSent();
ThrowIfReadOnly();
if (string.IsNullOrEmpty(value))
{
Remove(key);
@ -46,7 +46,7 @@ namespace Microsoft.Net.Http.Server
get { return Store[key]; }
set
{
ThrowIfSent();
ThrowIfReadOnly();
Store[key] = value;
}
}
@ -56,11 +56,6 @@ namespace Microsoft.Net.Http.Server
get { return Store.Count; }
}
public bool IsReadOnly
{
get { return Sent; }
}
public ICollection<string> Keys
{
get { return Store.Keys; }
@ -73,19 +68,19 @@ namespace Microsoft.Net.Http.Server
public void Add(KeyValuePair<string, string[]> item)
{
ThrowIfSent();
ThrowIfReadOnly();
Store.Add(item);
}
public void Add(string key, string[] value)
{
ThrowIfSent();
ThrowIfReadOnly();
Store.Add(key, value);
}
public void Append(string key, string value)
{
ThrowIfSent();
ThrowIfReadOnly();
string[] values;
if (Store.TryGetValue(key, out values))
{
@ -102,7 +97,7 @@ namespace Microsoft.Net.Http.Server
public void AppendValues(string key, params string[] values)
{
ThrowIfSent();
ThrowIfReadOnly();
string[] oldValues;
if (Store.TryGetValue(key, out oldValues))
{
@ -119,7 +114,7 @@ namespace Microsoft.Net.Http.Server
public void Clear()
{
ThrowIfSent();
ThrowIfReadOnly();
Store.Clear();
}
@ -165,25 +160,25 @@ namespace Microsoft.Net.Http.Server
public bool Remove(KeyValuePair<string, string[]> item)
{
ThrowIfSent();
ThrowIfReadOnly();
return Store.Remove(item);
}
public bool Remove(string key)
{
ThrowIfSent();
ThrowIfReadOnly();
return Store.Remove(key);
}
public void Set(string key, string value)
{
ThrowIfSent();
ThrowIfReadOnly();
Store[key] = new[] { value };
}
public void SetValues(string key, params string[] values)
{
ThrowIfSent();
ThrowIfReadOnly();
Store[key] = values;
}
@ -197,11 +192,11 @@ namespace Microsoft.Net.Http.Server
return GetEnumerator();
}
private void ThrowIfSent()
private void ThrowIfReadOnly()
{
if (Sent)
if (IsReadOnly)
{
throw new InvalidOperationException("The response headers cannot be modified because they have already been sent.");
throw new InvalidOperationException("The response headers cannot be modified because the response has already started.");
}
}
}

Просмотреть файл

@ -158,9 +158,9 @@ namespace Microsoft.Net.Http.Server
public Task<Stream> UpgradeAsync()
{
if (!IsUpgradableRequest || _response.HeadersSent)
if (!IsUpgradableRequest || _response.HasStarted)
{
throw new InvalidOperationException();
throw new InvalidOperationException("This request cannot be upgraded. It is incompatible, or the response has already started.");
}
// Set the status code and reason phrase

Просмотреть файл

@ -33,6 +33,7 @@ using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Framework.Logging;
using static Microsoft.Net.Http.Server.UnsafeNclNativeMethods;
namespace Microsoft.Net.Http.Server
{
@ -46,18 +47,34 @@ namespace Microsoft.Net.Http.Server
private ResponseStream _nativeStream;
private long _expectedBodyLength;
private BoundaryType _boundaryType;
private UnsafeNclNativeMethods.HttpApi.HTTP_RESPONSE_V2 _nativeResponse;
private HttpApi.HTTP_RESPONSE_V2 _nativeResponse;
private IList<Tuple<Action<object>, object>> _onSendingHeadersActions;
private IList<Tuple<Action<object>, object>> _onResponseCompletedActions;
private RequestContext _requestContext;
private bool _bufferingEnabled;
internal Response(RequestContext httpContext)
internal Response(RequestContext requestContext)
{
// TODO: Verbose log
_requestContext = httpContext;
_nativeResponse = new UnsafeNclNativeMethods.HttpApi.HTTP_RESPONSE_V2();
_requestContext = requestContext;
_headers = new HeaderCollection();
Reset();
}
public void Reset()
{
if (_responseState >= ResponseState.StartedSending)
{
_requestContext.Abort();
throw new InvalidOperationException("The response has already been sent. Request Aborted.");
}
// We haven't started yet, or we're just buffered, we can clear any data, headers, and state so
// that we can start over (e.g. to write an error message).
_nativeResponse = new HttpApi.HTTP_RESPONSE_V2();
_headers.IsReadOnly = false;
_headers.Clear();
_reasonPhrase = null;
_boundaryType = BoundaryType.None;
_nativeResponse.Response_V1.StatusCode = (ushort)HttpStatusCode.OK;
_nativeResponse.Response_V1.Version.MajorVersion = 1;
@ -65,13 +82,17 @@ namespace Microsoft.Net.Http.Server
_responseState = ResponseState.Created;
_onSendingHeadersActions = new List<Tuple<Action<object>, object>>();
_onResponseCompletedActions = new List<Tuple<Action<object>, object>>();
_bufferingEnabled = _requestContext.Server.BufferResponses;
_expectedBodyLength = 0;
_nativeStream = null;
}
private enum ResponseState
{
Created,
Started,
ComputedHeaders,
SentHeaders,
StartedSending,
Closed,
}
@ -105,14 +126,6 @@ namespace Microsoft.Net.Http.Server
}
}
private void CheckResponseStarted()
{
if (_responseState >= ResponseState.SentHeaders)
{
throw new InvalidOperationException("Headers already sent.");
}
}
public string ReasonPhrase
{
get { return _reasonPhrase; }
@ -124,6 +137,16 @@ namespace Microsoft.Net.Http.Server
}
}
public bool ShouldBuffer
{
get { return _bufferingEnabled; }
set
{
CheckResponseStarted();
_bufferingEnabled = value;
}
}
public Stream Body
{
get
@ -168,10 +191,7 @@ namespace Microsoft.Net.Http.Server
internal long ExpectedBodyLength
{
get
{
return _expectedBodyLength;
}
get { return _expectedBodyLength; }
}
// Header accessors
@ -248,6 +268,7 @@ namespace Microsoft.Net.Http.Server
{
return;
}
Start();
NotifyOnResponseCompleted();
// TODO: Verbose log
EnsureResponseStream();
@ -259,26 +280,30 @@ namespace Microsoft.Net.Http.Server
internal BoundaryType BoundaryType
{
get
{
return _boundaryType;
}
get { return _boundaryType; }
}
public bool HeadersSent
public bool HasStarted
{
get
get { return _responseState >= ResponseState.Started; }
}
private void CheckResponseStarted()
{
if (HasStarted)
{
return _responseState >= ResponseState.SentHeaders;
throw new InvalidOperationException("Headers already sent.");
}
}
internal bool ComputedHeaders
{
get
{
return _responseState >= ResponseState.ComputedHeaders;
}
get { return _responseState >= ResponseState.ComputedHeaders; }
}
public bool HasStartedSending
{
get { return _responseState >= ResponseState.StartedSending; }
}
private void EnsureResponseStream()
@ -319,14 +344,14 @@ namespace Microsoft.Net.Http.Server
// What would we loose by bypassing HttpSendHttpResponse?
//
// TODO: Consider using the HTTP_SEND_RESPONSE_FLAG_BUFFER_DATA flag for most/all responses rather than just Opaque.
internal unsafe uint SendHeaders(UnsafeNclNativeMethods.HttpApi.HTTP_DATA_CHUNK* pDataChunk,
internal unsafe uint SendHeaders(HttpApi.HTTP_DATA_CHUNK[] dataChunks,
ResponseStreamAsyncResult asyncResult,
UnsafeNclNativeMethods.HttpApi.HTTP_FLAGS flags,
HttpApi.HTTP_FLAGS flags,
bool isOpaqueUpgrade)
{
Debug.Assert(!HeadersSent, "HttpListenerResponse::SendHeaders()|SentHeaders is true.");
Debug.Assert(!HasStartedSending, "HttpListenerResponse::SendHeaders()|SentHeaders is true.");
_responseState = ResponseState.SentHeaders;
_responseState = ResponseState.StartedSending;
var reasonPhrase = GetReasonPhrase(StatusCode);
if (RequestContext.Logger.IsEnabled(LogLevel.Verbose))
@ -344,10 +369,16 @@ namespace Microsoft.Net.Http.Server
List<GCHandle> pinnedHeaders = SerializeHeaders(isOpaqueUpgrade);
try
{
if (pDataChunk != null)
if (dataChunks != null)
{
_nativeResponse.Response_V1.EntityChunkCount = 1;
_nativeResponse.Response_V1.pEntityChunks = pDataChunk;
if (pinnedHeaders == null)
{
pinnedHeaders = new List<GCHandle>();
}
var handle = GCHandle.Alloc(dataChunks, GCHandleType.Pinned);
pinnedHeaders.Add(handle);
_nativeResponse.Response_V1.EntityChunkCount = (ushort)dataChunks.Length;
_nativeResponse.Response_V1.pEntityChunks = (HttpApi.HTTP_DATA_CHUNK*)handle.AddrOfPinnedObject();
}
else if (asyncResult != null && asyncResult.DataChunks != null)
{
@ -360,45 +391,16 @@ namespace Microsoft.Net.Http.Server
_nativeResponse.Response_V1.pEntityChunks = null;
}
if (reasonPhrase.Length > 0)
byte[] reasonPhraseBytes = new byte[HeaderEncoding.GetByteCount(reasonPhrase)];
fixed (byte* pReasonPhrase = reasonPhraseBytes)
{
byte[] reasonPhraseBytes = new byte[HeaderEncoding.GetByteCount(reasonPhrase)];
fixed (byte* pReasonPhrase = reasonPhraseBytes)
{
_nativeResponse.Response_V1.ReasonLength = (ushort)reasonPhraseBytes.Length;
HeaderEncoding.GetBytes(reasonPhrase, 0, reasonPhraseBytes.Length, reasonPhraseBytes, 0);
_nativeResponse.Response_V1.pReason = (sbyte*)pReasonPhrase;
fixed (UnsafeNclNativeMethods.HttpApi.HTTP_RESPONSE_V2* pResponse = &_nativeResponse)
{
statusCode =
UnsafeNclNativeMethods.HttpApi.HttpSendHttpResponse(
RequestContext.RequestQueueHandle,
Request.RequestId,
(uint)flags,
pResponse,
null,
&bytesSent,
SafeLocalFree.Zero,
0,
asyncResult == null ? SafeNativeOverlapped.Zero : asyncResult.NativeOverlapped,
IntPtr.Zero);
if (asyncResult != null &&
statusCode == UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS &&
WebListener.SkipIOCPCallbackOnSuccess)
{
asyncResult.BytesSent = bytesSent;
// The caller will invoke IOCompleted
}
}
}
}
else
{
fixed (UnsafeNclNativeMethods.HttpApi.HTTP_RESPONSE_V2* pResponse = &_nativeResponse)
_nativeResponse.Response_V1.ReasonLength = (ushort)reasonPhraseBytes.Length;
HeaderEncoding.GetBytes(reasonPhrase, 0, reasonPhraseBytes.Length, reasonPhraseBytes, 0);
_nativeResponse.Response_V1.pReason = (sbyte*)pReasonPhrase;
fixed (HttpApi.HTTP_RESPONSE_V2* pResponse = &_nativeResponse)
{
statusCode =
UnsafeNclNativeMethods.HttpApi.HttpSendHttpResponse(
HttpApi.HttpSendHttpResponse(
RequestContext.RequestQueueHandle,
Request.RequestId,
(uint)flags,
@ -411,7 +413,7 @@ namespace Microsoft.Net.Http.Server
IntPtr.Zero);
if (asyncResult != null &&
statusCode == UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS &&
statusCode == ErrorCodes.ERROR_SUCCESS &&
WebListener.SkipIOCPCallbackOnSuccess)
{
asyncResult.BytesSent = bytesSent;
@ -427,10 +429,20 @@ namespace Microsoft.Net.Http.Server
return statusCode;
}
internal UnsafeNclNativeMethods.HttpApi.HTTP_FLAGS ComputeHeaders(bool endOfRequest = false)
internal void Start()
{
// Notify that this is absolutely the last chance to make changes.
NotifyOnSendingHeaders();
if (!HasStarted)
{
// Notify that this is absolutely the last chance to make changes.
NotifyOnSendingHeaders();
Headers.IsReadOnly = true; // Prohibit further modifications.
_responseState = ResponseState.Started;
}
}
internal HttpApi.HTTP_FLAGS ComputeHeaders(bool endOfRequest = false, int bufferedBytes = 0)
{
Headers.IsReadOnly = false; // Temporarily allow modification.
// 401
if (StatusCode == (ushort)HttpStatusCode.Unauthorized)
@ -438,7 +450,7 @@ namespace Microsoft.Net.Http.Server
RequestContext.Server.AuthenticationManager.SetAuthenticationChallenge(RequestContext);
}
var flags = UnsafeNclNativeMethods.HttpApi.HTTP_FLAGS.NONE;
var flags = HttpApi.HTTP_FLAGS.NONE;
Debug.Assert(!ComputedHeaders, "HttpListenerResponse::ComputeHeaders()|ComputedHeaders is true.");
_responseState = ResponseState.ComputedHeaders;
@ -478,14 +490,18 @@ namespace Microsoft.Net.Http.Server
// The application is performing it's own chunking.
_boundaryType = BoundaryType.PassThrough;
}
else if (endOfRequest && !(isHeadRequest && statusCanHaveBody)) // HEAD requests always end without a body. Assume a GET response would have a body.
else if (endOfRequest && !(isHeadRequest && statusCanHaveBody)) // HEAD requests should always end without a body. Assume a GET response would have a body.
{
if (statusCanHaveBody)
if (bufferedBytes > 0)
{
Headers[HttpKnownHeaderNames.ContentLength] = bufferedBytes.ToString(CultureInfo.InvariantCulture);
}
else if (statusCanHaveBody)
{
Headers[HttpKnownHeaderNames.ContentLength] = Constants.Zero;
}
_boundaryType = BoundaryType.ContentLength;
_expectedBodyLength = 0;
_expectedBodyLength = bufferedBytes;
}
else if (keepConnectionAlive && requestVersion == Constants.V1_1)
{
@ -508,9 +524,10 @@ namespace Microsoft.Net.Http.Server
{
Headers.Append(HttpKnownHeaderNames.Connection, Constants.Close);
}
flags = UnsafeNclNativeMethods.HttpApi.HTTP_FLAGS.HTTP_SEND_RESPONSE_FLAG_DISCONNECT;
flags = HttpApi.HTTP_FLAGS.HTTP_SEND_RESPONSE_FLAG_DISCONNECT;
}
Headers.IsReadOnly = true; // Prohibit further modifications.
return flags;
}
@ -521,9 +538,9 @@ namespace Microsoft.Net.Http.Server
private List<GCHandle> SerializeHeaders(bool isOpaqueUpgrade)
{
Headers.Sent = true; // Prohibit further modifications.
UnsafeNclNativeMethods.HttpApi.HTTP_UNKNOWN_HEADER[] unknownHeaders = null;
UnsafeNclNativeMethods.HttpApi.HTTP_RESPONSE_INFO[] knownHeaderInfo = null;
Headers.IsReadOnly = true; // Prohibit further modifications.
HttpApi.HTTP_UNKNOWN_HEADER[] unknownHeaders = null;
HttpApi.HTTP_RESPONSE_INFO[] knownHeaderInfo = null;
List<GCHandle> pinnedHeaders;
GCHandle gcHandle;
/*
@ -552,11 +569,11 @@ namespace Microsoft.Net.Http.Server
continue;
}
// See if this is an unknown header
lookup = UnsafeNclNativeMethods.HttpApi.HTTP_RESPONSE_HEADER_ID.IndexOfKnownHeader(headerPair.Key);
lookup = HttpApi.HTTP_RESPONSE_HEADER_ID.IndexOfKnownHeader(headerPair.Key);
// Http.Sys doesn't let us send the Connection: Upgrade header as a Known header.
if (lookup == -1 ||
(isOpaqueUpgrade && lookup == (int)UnsafeNclNativeMethods.HttpApi.HTTP_RESPONSE_HEADER_ID.Enum.HttpHeaderConnection))
(isOpaqueUpgrade && lookup == (int)HttpApi.HTTP_RESPONSE_HEADER_ID.Enum.HttpHeaderConnection))
{
numUnknownHeaders += headerPair.Value.Length;
}
@ -569,7 +586,7 @@ namespace Microsoft.Net.Http.Server
try
{
fixed (UnsafeNclNativeMethods.HttpApi.HTTP_KNOWN_HEADER* pKnownHeaders = &_nativeResponse.Response_V1.Headers.KnownHeaders)
fixed (HttpApi.HTTP_KNOWN_HEADER* pKnownHeaders = &_nativeResponse.Response_V1.Headers.KnownHeaders)
{
foreach (KeyValuePair<string, string[]> headerPair in Headers)
{
@ -580,18 +597,18 @@ namespace Microsoft.Net.Http.Server
}
headerName = headerPair.Key;
string[] headerValues = headerPair.Value;
lookup = UnsafeNclNativeMethods.HttpApi.HTTP_RESPONSE_HEADER_ID.IndexOfKnownHeader(headerName);
lookup = HttpApi.HTTP_RESPONSE_HEADER_ID.IndexOfKnownHeader(headerName);
// Http.Sys doesn't let us send the Connection: Upgrade header as a Known header.
if (lookup == -1 ||
(isOpaqueUpgrade && lookup == (int)UnsafeNclNativeMethods.HttpApi.HTTP_RESPONSE_HEADER_ID.Enum.HttpHeaderConnection))
(isOpaqueUpgrade && lookup == (int)HttpApi.HTTP_RESPONSE_HEADER_ID.Enum.HttpHeaderConnection))
{
if (unknownHeaders == null)
{
unknownHeaders = new UnsafeNclNativeMethods.HttpApi.HTTP_UNKNOWN_HEADER[numUnknownHeaders];
unknownHeaders = new HttpApi.HTTP_UNKNOWN_HEADER[numUnknownHeaders];
gcHandle = GCHandle.Alloc(unknownHeaders, GCHandleType.Pinned);
pinnedHeaders.Add(gcHandle);
_nativeResponse.Response_V1.Headers.pUnknownHeaders = (UnsafeNclNativeMethods.HttpApi.HTTP_UNKNOWN_HEADER*)gcHandle.AddrOfPinnedObject();
_nativeResponse.Response_V1.Headers.pUnknownHeaders = (HttpApi.HTTP_UNKNOWN_HEADER*)gcHandle.AddrOfPinnedObject();
}
for (int headerValueIndex = 0; headerValueIndex < headerValues.Length; headerValueIndex++)
@ -632,29 +649,29 @@ namespace Microsoft.Net.Http.Server
{
if (knownHeaderInfo == null)
{
knownHeaderInfo = new UnsafeNclNativeMethods.HttpApi.HTTP_RESPONSE_INFO[numKnownMultiHeaders];
knownHeaderInfo = new HttpApi.HTTP_RESPONSE_INFO[numKnownMultiHeaders];
gcHandle = GCHandle.Alloc(knownHeaderInfo, GCHandleType.Pinned);
pinnedHeaders.Add(gcHandle);
_nativeResponse.pResponseInfo = (UnsafeNclNativeMethods.HttpApi.HTTP_RESPONSE_INFO*)gcHandle.AddrOfPinnedObject();
_nativeResponse.pResponseInfo = (HttpApi.HTTP_RESPONSE_INFO*)gcHandle.AddrOfPinnedObject();
}
knownHeaderInfo[_nativeResponse.ResponseInfoCount].Type = UnsafeNclNativeMethods.HttpApi.HTTP_RESPONSE_INFO_TYPE.HttpResponseInfoTypeMultipleKnownHeaders;
knownHeaderInfo[_nativeResponse.ResponseInfoCount].Type = HttpApi.HTTP_RESPONSE_INFO_TYPE.HttpResponseInfoTypeMultipleKnownHeaders;
knownHeaderInfo[_nativeResponse.ResponseInfoCount].Length =
#if DNXCORE50
(uint)Marshal.SizeOf<UnsafeNclNativeMethods.HttpApi.HTTP_MULTIPLE_KNOWN_HEADERS>();
(uint)Marshal.SizeOf<HttpApi.HTTP_MULTIPLE_KNOWN_HEADERS>();
#else
(uint)Marshal.SizeOf(typeof(UnsafeNclNativeMethods.HttpApi.HTTP_MULTIPLE_KNOWN_HEADERS));
(uint)Marshal.SizeOf(typeof(HttpApi.HTTP_MULTIPLE_KNOWN_HEADERS));
#endif
UnsafeNclNativeMethods.HttpApi.HTTP_MULTIPLE_KNOWN_HEADERS header = new UnsafeNclNativeMethods.HttpApi.HTTP_MULTIPLE_KNOWN_HEADERS();
HttpApi.HTTP_MULTIPLE_KNOWN_HEADERS header = new HttpApi.HTTP_MULTIPLE_KNOWN_HEADERS();
header.HeaderId = (UnsafeNclNativeMethods.HttpApi.HTTP_RESPONSE_HEADER_ID.Enum)lookup;
header.Flags = UnsafeNclNativeMethods.HttpApi.HTTP_RESPONSE_INFO_FLAGS.PreserveOrder; // TODO: The docs say this is for www-auth only.
header.HeaderId = (HttpApi.HTTP_RESPONSE_HEADER_ID.Enum)lookup;
header.Flags = HttpApi.HTTP_RESPONSE_INFO_FLAGS.PreserveOrder; // TODO: The docs say this is for www-auth only.
UnsafeNclNativeMethods.HttpApi.HTTP_KNOWN_HEADER[] nativeHeaderValues = new UnsafeNclNativeMethods.HttpApi.HTTP_KNOWN_HEADER[headerValues.Length];
HttpApi.HTTP_KNOWN_HEADER[] nativeHeaderValues = new HttpApi.HTTP_KNOWN_HEADER[headerValues.Length];
gcHandle = GCHandle.Alloc(nativeHeaderValues, GCHandleType.Pinned);
pinnedHeaders.Add(gcHandle);
header.KnownHeaders = (UnsafeNclNativeMethods.HttpApi.HTTP_KNOWN_HEADER*)gcHandle.AddrOfPinnedObject();
header.KnownHeaders = (HttpApi.HTTP_KNOWN_HEADER*)gcHandle.AddrOfPinnedObject();
for (int headerValueIndex = 0; headerValueIndex < headerValues.Length; headerValueIndex++)
{
@ -672,7 +689,7 @@ namespace Microsoft.Net.Http.Server
// This type is a struct, not an object, so pinning it causes a boxed copy to be created. We can't do that until after all the fields are set.
gcHandle = GCHandle.Alloc(header, GCHandleType.Pinned);
pinnedHeaders.Add(gcHandle);
knownHeaderInfo[_nativeResponse.ResponseInfoCount].pInfo = (UnsafeNclNativeMethods.HttpApi.HTTP_MULTIPLE_KNOWN_HEADERS*)gcHandle.AddrOfPinnedObject();
knownHeaderInfo[_nativeResponse.ResponseInfoCount].pInfo = (HttpApi.HTTP_MULTIPLE_KNOWN_HEADERS*)gcHandle.AddrOfPinnedObject();
_nativeResponse.ResponseInfoCount++;
}
@ -704,18 +721,18 @@ namespace Microsoft.Net.Http.Server
// Subset of ComputeHeaders
internal void SendOpaqueUpgrade()
{
// TODO: Should we do this notification earlier when you still have a chance to change the status code to avoid an upgrade?
// Notify that this is absolutely the last chance to make changes.
NotifyOnSendingHeaders();
Start();
_boundaryType = BoundaryType.Close;
// TODO: Send headers async?
ulong errorCode = SendHeaders(null, null,
UnsafeNclNativeMethods.HttpApi.HTTP_FLAGS.HTTP_SEND_RESPONSE_FLAG_OPAQUE |
UnsafeNclNativeMethods.HttpApi.HTTP_FLAGS.HTTP_SEND_RESPONSE_FLAG_MORE_DATA |
UnsafeNclNativeMethods.HttpApi.HTTP_FLAGS.HTTP_SEND_RESPONSE_FLAG_BUFFER_DATA,
HttpApi.HTTP_FLAGS.HTTP_SEND_RESPONSE_FLAG_OPAQUE |
HttpApi.HTTP_FLAGS.HTTP_SEND_RESPONSE_FLAG_MORE_DATA |
HttpApi.HTTP_FLAGS.HTTP_SEND_RESPONSE_FLAG_BUFFER_DATA,
true);
if (errorCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS)
if (errorCode != ErrorCodes.ERROR_SUCCESS)
{
throw new WebListenerException((int)errorCode);
}
@ -725,7 +742,7 @@ namespace Microsoft.Net.Http.Server
{
if (_responseState >= ResponseState.Closed)
{
throw new ObjectDisposedException(this.GetType().FullName);
throw new ObjectDisposedException(GetType().FullName);
}
}
@ -746,6 +763,7 @@ namespace Microsoft.Net.Http.Server
internal void SwitchToOpaqueMode()
{
EnsureResponseStream();
_bufferingEnabled = false;
_nativeStream.SwitchToOpaqueMode();
}

Просмотреть файл

@ -22,23 +22,27 @@
// ------------------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.IO;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using static Microsoft.Net.Http.Server.UnsafeNclNativeMethods;
namespace Microsoft.Net.Http.Server
{
internal class ResponseStream : Stream
{
private static readonly byte[] ChunkTerminator = new byte[] { (byte)'0', (byte)'\r', (byte)'\n', (byte)'\r', (byte)'\n' };
private const int MaxBufferSize = 4 * 1024;
private RequestContext _requestContext;
private long _leftToWrite = long.MinValue;
private bool _closed;
private bool _inOpaqueMode;
private BufferBuilder _buffer = new BufferBuilder();
// The last write needs special handling to cancel.
private ResponseStreamAsyncResult _lastWrite;
@ -99,49 +103,185 @@ namespace Microsoft.Net.Http.Server
// Send headers
public override void Flush()
{
if (_closed || _requestContext.Response.HeadersSent)
if (_closed)
{
return;
}
UnsafeNclNativeMethods.HttpApi.HTTP_FLAGS flags = ComputeLeftToWrite();
// TODO: Verbose log
FlushInternal(endOfRequest: false);
}
private unsafe void FlushInternal(bool endOfRequest)
{
bool startedSending = _requestContext.Response.HasStartedSending;
var byteCount = _buffer.TotalBytes;
if (byteCount == 0 && startedSending && !endOfRequest)
{
// Empty flush
return;
}
var flags = ComputeLeftToWrite(endOfRequest);
if (!_inOpaqueMode && endOfRequest && _leftToWrite > byteCount)
{
_requestContext.Abort();
// This is logged rather than thrown because it is too late for an exception to be visible in user code.
LogHelper.LogError(_requestContext.Logger, "ResponseStream::Dispose", "Fewer bytes were written than were specified in the Content-Length.");
return;
}
if (endOfRequest && _requestContext.Response.BoundaryType == BoundaryType.Close)
{
flags |= HttpApi.HTTP_FLAGS.HTTP_SEND_RESPONSE_FLAG_DISCONNECT;
}
else if (!endOfRequest && _leftToWrite != byteCount)
{
flags |= HttpApi.HTTP_FLAGS.HTTP_SEND_RESPONSE_FLAG_MORE_DATA;
}
UpdateWritenCount((uint)byteCount);
uint statusCode = 0;
HttpApi.HTTP_DATA_CHUNK[] dataChunks;
var pinnedBuffers = PinDataBuffers(endOfRequest, out dataChunks);
try
{
uint statusCode;
unsafe
if (!startedSending)
{
// TODO: Don't add MoreData flag if content-length == 0?
flags |= UnsafeNclNativeMethods.HttpApi.HTTP_FLAGS.HTTP_SEND_RESPONSE_FLAG_MORE_DATA;
statusCode = _requestContext.Response.SendHeaders(null, null, flags, false);
statusCode = _requestContext.Response.SendHeaders(dataChunks, null, flags, false);
}
if (statusCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS && statusCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_HANDLE_EOF)
else
{
throw new IOException(string.Empty, new WebListenerException((int)statusCode));
fixed (HttpApi.HTTP_DATA_CHUNK* pDataChunks = dataChunks)
{
statusCode = HttpApi.HttpSendResponseEntityBody(
_requestContext.RequestQueueHandle,
_requestContext.RequestId,
(uint)flags,
(ushort)dataChunks.Length,
pDataChunks,
null,
SafeLocalFree.Zero,
0,
SafeNativeOverlapped.Zero,
IntPtr.Zero);
}
if (_requestContext.Server.IgnoreWriteExceptions)
{
statusCode = UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS;
}
}
}
catch (Exception e)
finally
{
LogHelper.LogException(_requestContext.Logger, "Flush", e);
FreeDataBuffers(pinnedBuffers);
_buffer.Clear();
}
if (statusCode != ErrorCodes.ERROR_SUCCESS && statusCode != ErrorCodes.ERROR_HANDLE_EOF
// Don't throw for disconnects, we were already finished with the response.
&& (!endOfRequest || (statusCode != ErrorCodes.ERROR_CONNECTION_INVALID && statusCode != ErrorCodes.ERROR_INVALID_PARAMETER)))
{
Exception exception = new IOException(string.Empty, new WebListenerException((int)statusCode));
LogHelper.LogException(_requestContext.Logger, "Flush", exception);
Abort();
throw;
throw exception;
}
}
// Send headers
public override Task FlushAsync(CancellationToken cancellationToken)
private List<GCHandle> PinDataBuffers(bool endOfRequest, out HttpApi.HTTP_DATA_CHUNK[] dataChunks)
{
if (_closed || _requestContext.Response.HeadersSent)
var pins = new List<GCHandle>();
var chunked = _requestContext.Response.BoundaryType == BoundaryType.Chunked;
var currentChunk = 0;
// Figure out how many data chunks
if (chunked && _buffer.TotalBytes == 0 && endOfRequest)
{
dataChunks = new HttpApi.HTTP_DATA_CHUNK[1];
SetDataChunk(dataChunks, ref currentChunk, pins, new ArraySegment<byte>(Helpers.ChunkTerminator));
return pins;
}
else if (_buffer.TotalBytes == 0)
{
// No data
dataChunks = new HttpApi.HTTP_DATA_CHUNK[0];
return pins;
}
var chunkCount = _buffer.BufferCount;
if (chunked)
{
// Chunk framing
chunkCount += 2;
if (endOfRequest)
{
// Chunk terminator
chunkCount += 1;
}
}
dataChunks = new HttpApi.HTTP_DATA_CHUNK[chunkCount];
if (chunked)
{
var chunkHeaderBuffer = Helpers.GetChunkHeader(_buffer.TotalBytes);
SetDataChunk(dataChunks, ref currentChunk, pins, chunkHeaderBuffer);
}
foreach (var buffer in _buffer.Buffers)
{
SetDataChunk(dataChunks, ref currentChunk, pins, buffer);
}
if (chunked)
{
SetDataChunk(dataChunks, ref currentChunk, pins, new ArraySegment<byte>(Helpers.CRLF));
if (endOfRequest)
{
SetDataChunk(dataChunks, ref currentChunk, pins, new ArraySegment<byte>(Helpers.ChunkTerminator));
}
}
return pins;
}
private static void SetDataChunk(HttpApi.HTTP_DATA_CHUNK[] chunks, ref int chunkIndex, List<GCHandle> pins, ArraySegment<byte> buffer)
{
var handle = GCHandle.Alloc(buffer.Array, GCHandleType.Pinned);
pins.Add(handle);
chunks[chunkIndex].DataChunkType = HttpApi.HTTP_DATA_CHUNK_TYPE.HttpDataChunkFromMemory;
chunks[chunkIndex].fromMemory.pBuffer = handle.AddrOfPinnedObject() + buffer.Offset;
chunks[chunkIndex].fromMemory.BufferLength = (uint)buffer.Count;
chunkIndex++;
}
private void FreeDataBuffers(List<GCHandle> pinnedBuffers)
{
foreach (var pin in pinnedBuffers)
{
if (pin.IsAllocated)
{
pin.Free();
}
}
}
// Simpler than Flush because it will never be called at the end of the request from Dispose.
public unsafe override Task FlushAsync(CancellationToken cancellationToken)
{
if (_closed)
{
return Helpers.CompletedTask();
}
UnsafeNclNativeMethods.HttpApi.HTTP_FLAGS flags = ComputeLeftToWrite();
// TODO: Verbose log
if (cancellationToken.IsCancellationRequested)
bool startedSending = _requestContext.Response.HasStartedSending;
var byteCount = _buffer.TotalBytes;
if (byteCount == 0 && startedSending)
{
return Helpers.CanceledTask<int>();
// Empty flush
return Helpers.CompletedTask();
}
var cancellationRegistration = default(CancellationTokenRegistration);
@ -150,26 +290,37 @@ namespace Microsoft.Net.Http.Server
cancellationRegistration = cancellationToken.Register(RequestContext.AbortDelegate, _requestContext);
}
// TODO: Don't add MoreData flag if content-length == 0?
flags |= UnsafeNclNativeMethods.HttpApi.HTTP_FLAGS.HTTP_SEND_RESPONSE_FLAG_MORE_DATA;
ResponseStreamAsyncResult asyncResult = new ResponseStreamAsyncResult(this, null, null, null, 0, 0, _requestContext.Response.BoundaryType == BoundaryType.Chunked, false, cancellationRegistration);
var flags = ComputeLeftToWrite();
if (_leftToWrite != byteCount)
{
flags |= HttpApi.HTTP_FLAGS.HTTP_SEND_RESPONSE_FLAG_MORE_DATA;
}
UpdateWritenCount((uint)byteCount);
uint statusCode = 0;
var chunked = _requestContext.Response.BoundaryType == BoundaryType.Chunked;
var asyncResult = new ResponseStreamAsyncResult(this, _buffer, chunked, cancellationRegistration);
uint bytesSent = 0;
try
{
uint statusCode;
unsafe
if (!startedSending)
{
statusCode = _requestContext.Response.SendHeaders(null, asyncResult, flags, false);
bytesSent = asyncResult.BytesSent;
}
if (statusCode == UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS && WebListener.SkipIOCPCallbackOnSuccess)
else
{
// IO operation completed synchronously - callback won't be called to signal completion.
asyncResult.IOCompleted(statusCode);
}
else if (statusCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS && statusCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_IO_PENDING)
{
throw new IOException(string.Empty, new WebListenerException((int)statusCode));
statusCode = HttpApi.HttpSendResponseEntityBody(
_requestContext.RequestQueueHandle,
_requestContext.RequestId,
(uint)flags,
asyncResult.DataChunkCount,
asyncResult.DataChunks,
&bytesSent,
SafeLocalFree.Zero,
0,
asyncResult.NativeOverlapped,
IntPtr.Zero);
}
}
catch (Exception e)
@ -180,6 +331,34 @@ namespace Microsoft.Net.Http.Server
throw;
}
if (statusCode != ErrorCodes.ERROR_SUCCESS && statusCode != ErrorCodes.ERROR_IO_PENDING)
{
asyncResult.Dispose();
if (_requestContext.Server.IgnoreWriteExceptions && startedSending)
{
asyncResult.Complete();
}
else
{
Exception exception = new IOException(string.Empty, new WebListenerException((int)statusCode));
LogHelper.LogException(_requestContext.Logger, "FlushAsync", exception);
Abort();
throw exception;
}
}
if (statusCode == ErrorCodes.ERROR_SUCCESS && WebListener.SkipIOCPCallbackOnSuccess)
{
// IO operation completed synchronously - callback won't be called to signal completion.
asyncResult.IOCompleted(statusCode, bytesSent);
}
// Last write, cache it for special cancellation handling.
if ((flags & HttpApi.HTTP_FLAGS.HTTP_SEND_RESPONSE_FLAG_MORE_DATA) == 0)
{
_lastWrite = asyncResult;
}
return asyncResult.Task;
}
@ -195,13 +374,13 @@ namespace Microsoft.Net.Http.Server
throw new NotSupportedException(Resources.Exception_NoSeek);
}
public override int Read([In, Out] byte[] buffer, int offset, int size)
public override int Read([In, Out] byte[] buffer, int offset, int count)
{
throw new InvalidOperationException(Resources.Exception_WriteOnlyStream);
}
#if !DNXCORE50
public override IAsyncResult BeginRead(byte[] buffer, int offset, int size, AsyncCallback callback, object state)
public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback callback, object state)
{
throw new InvalidOperationException(Resources.Exception_WriteOnlyStream);
}
@ -225,7 +404,7 @@ namespace Microsoft.Net.Http.Server
UnsafeNclNativeMethods.HttpApi.HTTP_FLAGS flags = UnsafeNclNativeMethods.HttpApi.HTTP_FLAGS.NONE;
if (!_requestContext.Response.ComputedHeaders)
{
flags = _requestContext.Response.ComputeHeaders(endOfRequest: endOfRequest);
flags = _requestContext.Response.ComputeHeaders(endOfRequest, _buffer.TotalBytes);
}
if (_leftToWrite == long.MinValue)
{
@ -246,229 +425,56 @@ namespace Microsoft.Net.Http.Server
return flags;
}
public override unsafe void Write(byte[] buffer, int offset, int size)
public override void Write(byte[] buffer, int offset, int count)
{
if (buffer == null)
{
throw new ArgumentNullException("buffer");
}
if (offset < 0 || offset > buffer.Length)
{
throw new ArgumentOutOfRangeException("offset");
}
if (size < 0 || size > buffer.Length - offset)
{
throw new ArgumentOutOfRangeException("size");
}
if (_closed)
{
throw new ObjectDisposedException(GetType().FullName);
}
// Validates for null and bounds. Allows count == 0.
var data = new ArraySegment<byte>(buffer, offset, count);
CheckDisposed();
// TODO: Verbose log parameters
UnsafeNclNativeMethods.HttpApi.HTTP_FLAGS flags = ComputeLeftToWrite();
if (size == 0 && _leftToWrite != 0)
{
return;
}
if (_leftToWrite >= 0 && size > _leftToWrite)
{
throw new InvalidOperationException(Resources.Exception_TooMuchWritten);
}
// TODO: Verbose log
// Officially starts the response and fires OnSendingHeaders
_requestContext.Response.Start();
uint statusCode;
uint dataToWrite = (uint)size;
SafeLocalFree bufferAsIntPtr = null;
IntPtr pBufferAsIntPtr = IntPtr.Zero;
bool sentHeaders = _requestContext.Response.HeadersSent;
try
var currentBytes = _buffer.TotalBytes + data.Count;
var contentLength = _requestContext.Response.ContentLength;
if (contentLength.HasValue && !_requestContext.Response.ComputedHeaders && contentLength.Value <= currentBytes)
{
if (size == 0)
if (contentLength.Value < currentBytes)
{
// TODO: Is this code path accessible? Is this like a Flush?
statusCode = _requestContext.Response.SendHeaders(null, null, flags, false);
}
else
{
fixed (byte* pDataBuffer = buffer)
{
byte* pBuffer = pDataBuffer;
if (_requestContext.Response.BoundaryType == BoundaryType.Chunked)
{
// TODO:
// here we need some heuristics, some time it is definitely better to split this in 3 write calls
// but for small writes it is probably good enough to just copy the data internally.
string chunkHeader = size.ToString("x", CultureInfo.InvariantCulture);
dataToWrite = dataToWrite + (uint)(chunkHeader.Length + 4);
bufferAsIntPtr = SafeLocalFree.LocalAlloc((int)dataToWrite);
pBufferAsIntPtr = bufferAsIntPtr.DangerousGetHandle();
for (int i = 0; i < chunkHeader.Length; i++)
{
Marshal.WriteByte(pBufferAsIntPtr, i, (byte)chunkHeader[i]);
}
Marshal.WriteInt16(pBufferAsIntPtr, chunkHeader.Length, 0x0A0D);
Marshal.Copy(buffer, offset, IntPtrHelper.Add(pBufferAsIntPtr, chunkHeader.Length + 2), size);
Marshal.WriteInt16(pBufferAsIntPtr, (int)(dataToWrite - 2), 0x0A0D);
pBuffer = (byte*)pBufferAsIntPtr;
offset = 0;
}
UnsafeNclNativeMethods.HttpApi.HTTP_DATA_CHUNK dataChunk = new UnsafeNclNativeMethods.HttpApi.HTTP_DATA_CHUNK();
dataChunk.DataChunkType = UnsafeNclNativeMethods.HttpApi.HTTP_DATA_CHUNK_TYPE.HttpDataChunkFromMemory;
dataChunk.fromMemory.pBuffer = (IntPtr)(pBuffer + offset);
dataChunk.fromMemory.BufferLength = dataToWrite;
flags |= _leftToWrite == size ? UnsafeNclNativeMethods.HttpApi.HTTP_FLAGS.NONE : UnsafeNclNativeMethods.HttpApi.HTTP_FLAGS.HTTP_SEND_RESPONSE_FLAG_MORE_DATA;
if (!sentHeaders)
{
statusCode = _requestContext.Response.SendHeaders(&dataChunk, null, flags, false);
}
else
{
statusCode =
UnsafeNclNativeMethods.HttpApi.HttpSendResponseEntityBody(
_requestContext.RequestQueueHandle,
_requestContext.RequestId,
(uint)flags,
1,
&dataChunk,
null,
SafeLocalFree.Zero,
0,
SafeNativeOverlapped.Zero,
IntPtr.Zero);
if (_requestContext.Server.IgnoreWriteExceptions)
{
statusCode = UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS;
}
}
}
throw new InvalidOperationException("More bytes written than specified in the Content-Length header.");
}
// or the last write in a response that hasn't started yet, flush immideately
_buffer.Add(data);
Flush();
}
finally
// The last write in a response that has already started, flush immidately
else if (_requestContext.Response.ComputedHeaders && _leftToWrite >= 0 && _leftToWrite <= currentBytes)
{
if (bufferAsIntPtr != null)
if (_leftToWrite < currentBytes)
{
// free unmanaged buffer
bufferAsIntPtr.Dispose();
throw new InvalidOperationException("More bytes written than specified in the Content-Length header.");
}
_buffer.Add(data);
Flush();
}
if (statusCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS && statusCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_HANDLE_EOF)
else if (_requestContext.Response.ShouldBuffer && currentBytes < MaxBufferSize)
{
Exception exception = new IOException(string.Empty, new WebListenerException((int)statusCode));
LogHelper.LogException(_requestContext.Logger, "Write", exception);
Abort();
throw exception;
_buffer.CopyAndAdd(data);
}
else
{
// Append to existing data without a copy, and then flush immidately
_buffer.Add(data);
Flush();
}
UpdateWritenCount(dataToWrite);
// TODO: Verbose log data written
}
#if DNXCORE50
public unsafe IAsyncResult BeginWrite(byte[] buffer, int offset, int size, AsyncCallback callback, object state)
public unsafe IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback callback, object state)
#else
public override unsafe IAsyncResult BeginWrite(byte[] buffer, int offset, int size, AsyncCallback callback, object state)
public override unsafe IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback callback, object state)
#endif
{
if (buffer == null)
{
throw new ArgumentNullException("buffer");
}
if (offset < 0 || offset > buffer.Length)
{
throw new ArgumentOutOfRangeException("offset");
}
if (size < 0 || size > buffer.Length - offset)
{
throw new ArgumentOutOfRangeException("size");
}
if (_closed)
{
throw new ObjectDisposedException(GetType().FullName);
}
UnsafeNclNativeMethods.HttpApi.HTTP_FLAGS flags = ComputeLeftToWrite();
if (size == 0 && _leftToWrite != 0)
{
ResponseStreamAsyncResult result = new ResponseStreamAsyncResult(this, state, callback);
result.Complete();
return result;
}
if (_leftToWrite >= 0 && size > _leftToWrite)
{
throw new InvalidOperationException(Resources.Exception_TooMuchWritten);
}
// TODO: Verbose log parameters
uint statusCode;
uint bytesSent = 0;
flags |= _leftToWrite == size ? UnsafeNclNativeMethods.HttpApi.HTTP_FLAGS.NONE : UnsafeNclNativeMethods.HttpApi.HTTP_FLAGS.HTTP_SEND_RESPONSE_FLAG_MORE_DATA;
bool sentHeaders = _requestContext.Response.HeadersSent;
ResponseStreamAsyncResult asyncResult = new ResponseStreamAsyncResult(this, state, callback, buffer, offset, size, _requestContext.Response.BoundaryType == BoundaryType.Chunked, sentHeaders);
// Update m_LeftToWrite now so we can queue up additional BeginWrite's without waiting for EndWrite.
UpdateWritenCount((uint)((_requestContext.Response.BoundaryType == BoundaryType.Chunked) ? 0 : size));
try
{
if (!sentHeaders)
{
statusCode = _requestContext.Response.SendHeaders(null, asyncResult, flags, false);
bytesSent = asyncResult.BytesSent;
}
else
{
statusCode =
UnsafeNclNativeMethods.HttpApi.HttpSendResponseEntityBody(
_requestContext.RequestQueueHandle,
_requestContext.RequestId,
(uint)flags,
asyncResult.DataChunkCount,
asyncResult.DataChunks,
&bytesSent,
SafeLocalFree.Zero,
0,
asyncResult.NativeOverlapped,
IntPtr.Zero);
}
}
catch (Exception e)
{
LogHelper.LogException(_requestContext.Logger, "BeginWrite", e);
asyncResult.Dispose();
Abort();
throw;
}
if (statusCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS && statusCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_IO_PENDING)
{
asyncResult.Dispose();
if (_requestContext.Server.IgnoreWriteExceptions && sentHeaders)
{
asyncResult.Complete();
}
else
{
Exception exception = new IOException(string.Empty, new WebListenerException((int)statusCode));
LogHelper.LogException(_requestContext.Logger, "BeginWrite", exception);
Abort();
throw exception;
}
}
if (statusCode == UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS && WebListener.SkipIOCPCallbackOnSuccess)
{
// IO operation completed synchronously - callback won't be called to signal completion.
asyncResult.IOCompleted(statusCode, bytesSent);
}
// Last write, cache it for special cancellation handling.
if ((flags & UnsafeNclNativeMethods.HttpApi.HTTP_FLAGS.HTTP_SEND_RESPONSE_FLAG_MORE_DATA) == 0)
{
_lastWrite = asyncResult;
}
return asyncResult;
return WriteAsync(buffer, offset, count).ToIAsyncResult(callback, state);
}
#if DNXCORE50
public void EndWrite(IAsyncResult asyncResult)
@ -480,143 +486,58 @@ namespace Microsoft.Net.Http.Server
{
throw new ArgumentNullException("asyncResult");
}
ResponseStreamAsyncResult castedAsyncResult = asyncResult as ResponseStreamAsyncResult;
if (castedAsyncResult == null || castedAsyncResult.ResponseStream != this)
{
throw new ArgumentException(Resources.Exception_WrongIAsyncResult, "asyncResult");
}
if (castedAsyncResult.EndCalled)
{
throw new InvalidOperationException(Resources.Exception_EndCalledMultipleTimes);
}
castedAsyncResult.EndCalled = true;
try
{
// wait & then check for errors
// TODO: Graceful re-throw
castedAsyncResult.Task.Wait();
}
catch (Exception exception)
{
LogHelper.LogException(_requestContext.Logger, "EndWrite", exception);
Abort();
throw;
}
((Task)asyncResult).GetAwaiter().GetResult();
}
public override unsafe Task WriteAsync(byte[] buffer, int offset, int size, CancellationToken cancellationToken)
public override unsafe Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{
if (buffer == null)
{
throw new ArgumentNullException("buffer");
}
if (offset < 0 || offset > buffer.Length)
{
throw new ArgumentOutOfRangeException("offset");
}
if (size < 0 || size > buffer.Length - offset)
{
throw new ArgumentOutOfRangeException("size");
}
if (_closed)
{
throw new ObjectDisposedException(GetType().FullName);
}
UnsafeNclNativeMethods.HttpApi.HTTP_FLAGS flags = ComputeLeftToWrite();
if (size == 0 && _leftToWrite != 0)
{
return Helpers.CompletedTask();
}
if (_leftToWrite >= 0 && size > _leftToWrite)
{
throw new InvalidOperationException(Resources.Exception_TooMuchWritten);
}
// TODO: Verbose log
// Validates for null and bounds. Allows count == 0.
var data = new ArraySegment<byte>(buffer, offset, count);
if (cancellationToken.IsCancellationRequested)
{
return Helpers.CanceledTask<int>();
}
CheckDisposed();
// TODO: Verbose log parameters
// Officially starts the response and fires OnSendingHeaders
_requestContext.Response.Start();
var cancellationRegistration = default(CancellationTokenRegistration);
if (cancellationToken.CanBeCanceled)
var currentBytes = _buffer.TotalBytes + data.Count;
var contentLength = _requestContext.Response.ContentLength;
if (contentLength.HasValue && !_requestContext.Response.ComputedHeaders && contentLength.Value <= currentBytes)
{
cancellationRegistration = cancellationToken.Register(RequestContext.AbortDelegate, _requestContext);
}
uint statusCode;
uint bytesSent = 0;
flags |= _leftToWrite == size ? UnsafeNclNativeMethods.HttpApi.HTTP_FLAGS.NONE : UnsafeNclNativeMethods.HttpApi.HTTP_FLAGS.HTTP_SEND_RESPONSE_FLAG_MORE_DATA;
bool sentHeaders = _requestContext.Response.HeadersSent;
ResponseStreamAsyncResult asyncResult = new ResponseStreamAsyncResult(this, null, null, buffer, offset, size, _requestContext.Response.BoundaryType == BoundaryType.Chunked, sentHeaders, cancellationRegistration);
// Update m_LeftToWrite now so we can queue up additional BeginWrite's without waiting for EndWrite.
UpdateWritenCount((uint)((_requestContext.Response.BoundaryType == BoundaryType.Chunked) ? 0 : size));
try
{
if (!sentHeaders)
if (contentLength.Value < currentBytes)
{
statusCode = _requestContext.Response.SendHeaders(null, asyncResult, flags, false);
bytesSent = asyncResult.BytesSent;
throw new InvalidOperationException("More bytes written than specified in the Content-Length header.");
}
else
// The last write in a response that hasn't started yet, flush immideately
_buffer.Add(data);
return FlushAsync(cancellationToken);
}
// The last write in a response that has already started, flush immidately
else if (_requestContext.Response.ComputedHeaders && _leftToWrite > 0 && _leftToWrite <= currentBytes)
{
if (_leftToWrite < currentBytes)
{
statusCode =
UnsafeNclNativeMethods.HttpApi.HttpSendResponseEntityBody(
_requestContext.RequestQueueHandle,
_requestContext.RequestId,
(uint)flags,
asyncResult.DataChunkCount,
asyncResult.DataChunks,
&bytesSent,
SafeLocalFree.Zero,
0,
asyncResult.NativeOverlapped,
IntPtr.Zero);
throw new InvalidOperationException("More bytes written than specified in the Content-Length header.");
}
_buffer.Add(data);
return FlushAsync(cancellationToken);
}
catch (Exception e)
else if (_requestContext.Response.ShouldBuffer && currentBytes < MaxBufferSize)
{
LogHelper.LogException(_requestContext.Logger, "WriteAsync", e);
asyncResult.Dispose();
Abort();
throw;
_buffer.CopyAndAdd(data);
return Helpers.CompletedTask();
}
if (statusCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS && statusCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_IO_PENDING)
else
{
asyncResult.Dispose();
if (_requestContext.Server.IgnoreWriteExceptions && sentHeaders)
{
asyncResult.Complete();
}
else
{
Exception exception = new IOException(string.Empty, new WebListenerException((int)statusCode));
LogHelper.LogException(_requestContext.Logger, "WriteAsync", exception);
Abort();
throw exception;
}
// Append to existing data without a copy, and then flush immidately
_buffer.Add(data);
return FlushAsync(cancellationToken);
}
if (statusCode == UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS && WebListener.SkipIOCPCallbackOnSuccess)
{
// IO operation completed synchronously - callback won't be called to signal completion.
asyncResult.IOCompleted(statusCode, bytesSent);
}
// Last write, cache it for special cancellation handling.
if ((flags & UnsafeNclNativeMethods.HttpApi.HTTP_FLAGS.HTTP_SEND_RESPONSE_FLAG_MORE_DATA) == 0)
{
_lastWrite = asyncResult;
}
return asyncResult.Task;
}
internal unsafe Task SendFileAsync(string fileName, long offset, long? size, CancellationToken cancellationToken)
internal async Task SendFileAsync(string fileName, long offset, long? count, CancellationToken cancellationToken)
{
// It's too expensive to validate the file attributes before opening the file. Open the file and then check the lengths.
// This all happens inside of ResponseStreamAsyncResult.
@ -624,17 +545,26 @@ namespace Microsoft.Net.Http.Server
{
throw new ArgumentNullException("fileName");
}
if (_closed)
CheckDisposed();
if (_buffer.TotalBytes > 0)
{
throw new ObjectDisposedException(GetType().FullName);
// SendFileAsync is primarly used for full responses so we don't optimize this partialy buffered scenario.
// In theory we could merge SendFileAsyncCore into FlushAsync[Internal] and send the buffered data in the same call as the file.
await FlushAsync(cancellationToken);
}
// We can't mix await and unsafe so seperate the unsafe code into another method.
await SendFileAsyncCore(fileName, offset, count, cancellationToken);
}
internal unsafe Task SendFileAsyncCore(string fileName, long offset, long? count, CancellationToken cancellationToken)
{
_requestContext.Response.Start();
UnsafeNclNativeMethods.HttpApi.HTTP_FLAGS flags = ComputeLeftToWrite();
if (size == 0 && _leftToWrite != 0)
if (count == 0 && _leftToWrite != 0)
{
return Helpers.CompletedTask();
}
if (_leftToWrite >= 0 && size > _leftToWrite)
if (_leftToWrite >= 0 && count > _leftToWrite)
{
throw new InvalidOperationException(Resources.Exception_TooMuchWritten);
}
@ -653,30 +583,30 @@ namespace Microsoft.Net.Http.Server
uint statusCode;
uint bytesSent = 0;
flags |= _leftToWrite == size ? UnsafeNclNativeMethods.HttpApi.HTTP_FLAGS.NONE : UnsafeNclNativeMethods.HttpApi.HTTP_FLAGS.HTTP_SEND_RESPONSE_FLAG_MORE_DATA;
bool sentHeaders = _requestContext.Response.HeadersSent;
ResponseStreamAsyncResult asyncResult = new ResponseStreamAsyncResult(this, null, null, fileName, offset, size,
_requestContext.Response.BoundaryType == BoundaryType.Chunked, sentHeaders, cancellationRegistration);
flags |= _leftToWrite == count ? HttpApi.HTTP_FLAGS.NONE : HttpApi.HTTP_FLAGS.HTTP_SEND_RESPONSE_FLAG_MORE_DATA;
bool startedSending = _requestContext.Response.HasStartedSending;
var chunked = _requestContext.Response.BoundaryType == BoundaryType.Chunked;
ResponseStreamAsyncResult asyncResult = new ResponseStreamAsyncResult(this, fileName, offset, count, chunked, cancellationRegistration);
long bytesWritten;
if (_requestContext.Response.BoundaryType == BoundaryType.Chunked)
if (chunked)
{
bytesWritten = 0;
}
else if (size.HasValue)
else if (count.HasValue)
{
bytesWritten = size.Value;
bytesWritten = count.Value;
}
else
{
bytesWritten = asyncResult.FileLength - offset;
}
// Update m_LeftToWrite now so we can queue up additional calls to SendFileAsync.
// Update _leftToWrite now so we can queue up additional calls to SendFileAsync.
UpdateWritenCount((uint)bytesWritten);
try
{
if (!sentHeaders)
if (!startedSending)
{
statusCode = _requestContext.Response.SendHeaders(null, asyncResult, flags, false);
bytesSent = asyncResult.BytesSent;
@ -684,8 +614,7 @@ namespace Microsoft.Net.Http.Server
else
{
// TODO: If opaque then include the buffer data flag.
statusCode =
UnsafeNclNativeMethods.HttpApi.HttpSendResponseEntityBody(
statusCode = HttpApi.HttpSendResponseEntityBody(
_requestContext.RequestQueueHandle,
_requestContext.RequestId,
(uint)flags,
@ -709,7 +638,7 @@ namespace Microsoft.Net.Http.Server
if (statusCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS && statusCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_IO_PENDING)
{
asyncResult.Dispose();
if (_requestContext.Server.IgnoreWriteExceptions && sentHeaders)
if (_requestContext.Server.IgnoreWriteExceptions && startedSending)
{
asyncResult.Complete();
}
@ -722,14 +651,14 @@ namespace Microsoft.Net.Http.Server
}
}
if (statusCode == UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS && WebListener.SkipIOCPCallbackOnSuccess)
if (statusCode == ErrorCodes.ERROR_SUCCESS && WebListener.SkipIOCPCallbackOnSuccess)
{
// IO operation completed synchronously - callback won't be called to signal completion.
asyncResult.IOCompleted(statusCode, bytesSent);
}
// Last write, cache it for special cancellation handling.
if ((flags & UnsafeNclNativeMethods.HttpApi.HTTP_FLAGS.HTTP_SEND_RESPONSE_FLAG_MORE_DATA) == 0)
if ((flags & HttpApi.HTTP_FLAGS.HTTP_SEND_RESPONSE_FLAG_MORE_DATA) == 0)
{
_lastWrite = asyncResult;
}
@ -765,85 +694,7 @@ namespace Microsoft.Net.Http.Server
return;
}
_closed = true;
UnsafeNclNativeMethods.HttpApi.HTTP_FLAGS flags = ComputeLeftToWrite(endOfRequest: true);
if (_leftToWrite > 0 && !_inOpaqueMode)
{
_requestContext.Abort();
// This is logged rather than thrown because it is too late for an exception to be visible in user code.
LogHelper.LogError(_requestContext.Logger, "ResponseStream::Dispose", "Fewer bytes were written than were specified in the Content-Length.");
return;
}
bool sentHeaders = _requestContext.Response.HeadersSent;
if (sentHeaders && _leftToWrite == 0)
{
return;
}
uint statusCode = 0;
if ((_requestContext.Response.BoundaryType == BoundaryType.Chunked
|| _requestContext.Response.BoundaryType == BoundaryType.Close
|| _requestContext.Response.BoundaryType == BoundaryType.PassThrough)
&& !_requestContext.Request.IsHeadMethod)
{
if (_requestContext.Response.BoundaryType == BoundaryType.Close)
{
flags |= UnsafeNclNativeMethods.HttpApi.HTTP_FLAGS.HTTP_SEND_RESPONSE_FLAG_DISCONNECT;
}
fixed (void* pBuffer = ChunkTerminator)
{
UnsafeNclNativeMethods.HttpApi.HTTP_DATA_CHUNK* pDataChunk = null;
if (_requestContext.Response.BoundaryType == BoundaryType.Chunked)
{
UnsafeNclNativeMethods.HttpApi.HTTP_DATA_CHUNK dataChunk = new UnsafeNclNativeMethods.HttpApi.HTTP_DATA_CHUNK();
dataChunk.DataChunkType = UnsafeNclNativeMethods.HttpApi.HTTP_DATA_CHUNK_TYPE.HttpDataChunkFromMemory;
dataChunk.fromMemory.pBuffer = (IntPtr)pBuffer;
dataChunk.fromMemory.BufferLength = (uint)ChunkTerminator.Length;
pDataChunk = &dataChunk;
}
if (!sentHeaders)
{
statusCode = _requestContext.Response.SendHeaders(pDataChunk, null, flags, false);
}
else
{
statusCode =
UnsafeNclNativeMethods.HttpApi.HttpSendResponseEntityBody(
_requestContext.RequestQueueHandle,
_requestContext.RequestId,
(uint)flags,
pDataChunk != null ? (ushort)1 : (ushort)0,
pDataChunk,
null,
SafeLocalFree.Zero,
0,
SafeNativeOverlapped.Zero,
IntPtr.Zero);
if (_requestContext.Server.IgnoreWriteExceptions)
{
statusCode = UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS;
}
}
}
}
else
{
if (!sentHeaders)
{
statusCode = _requestContext.Response.SendHeaders(null, null, flags, false);
}
}
if (statusCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS && statusCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_HANDLE_EOF
// Don't throw for disconnects, we were already finished with the response.
&& statusCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_CONNECTION_INVALID
&& statusCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_INVALID_PARAMETER)
{
Exception exception = new IOException(string.Empty, new WebListenerException((int)statusCode));
LogHelper.LogException(_requestContext.Logger, "Dispose", exception);
_requestContext.Abort();
throw exception;
}
_leftToWrite = 0;
FlushInternal(endOfRequest: true);
}
}
finally
@ -870,5 +721,13 @@ namespace Microsoft.Net.Http.Server
UnsafeNclNativeMethods.CancelIoEx(requestQueueHandle, asyncState.NativeOverlapped);
}
}
private void CheckDisposed()
{
if (_closed)
{
throw new ObjectDisposedException(GetType().FullName);
}
}
}
}

Просмотреть файл

@ -27,117 +27,97 @@ using System.IO;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using static Microsoft.Net.Http.Server.UnsafeNclNativeMethods;
namespace Microsoft.Net.Http.Server
{
internal unsafe class ResponseStreamAsyncResult : IAsyncResult, IDisposable
{
private static readonly byte[] CRLF = new byte[] { (byte)'\r', (byte)'\n' };
private static readonly IOCompletionCallback IOCallback = new IOCompletionCallback(Callback);
private SafeNativeOverlapped _overlapped;
private UnsafeNclNativeMethods.HttpApi.HTTP_DATA_CHUNK[] _dataChunks;
private bool _sentHeaders;
private HttpApi.HTTP_DATA_CHUNK[] _dataChunks;
private FileStream _fileStream;
private ResponseStream _responseStream;
private TaskCompletionSource<object> _tcs;
private AsyncCallback _callback;
private uint _bytesSent;
private CancellationTokenRegistration _cancellationRegistration;
internal ResponseStreamAsyncResult(ResponseStream responseStream, object userState, AsyncCallback callback)
internal ResponseStreamAsyncResult(ResponseStream responseStream, CancellationTokenRegistration cancellationRegistration)
{
_responseStream = responseStream;
_tcs = new TaskCompletionSource<object>(userState);
_callback = callback;
}
internal ResponseStreamAsyncResult(ResponseStream responseStream, object userState, AsyncCallback callback,
byte[] buffer, int offset, int size, bool chunked, bool sentHeaders)
: this(responseStream, userState, callback, buffer, offset, size, chunked, sentHeaders,
new CancellationTokenRegistration())
{
}
internal ResponseStreamAsyncResult(ResponseStream responseStream, object userState, AsyncCallback callback,
byte[] buffer, int offset, int size, bool chunked, bool sentHeaders,
CancellationTokenRegistration cancellationRegistration)
: this(responseStream, userState, callback)
{
_sentHeaders = sentHeaders;
_tcs = new TaskCompletionSource<object>();
_cancellationRegistration = cancellationRegistration;
var boundHandle = _responseStream.RequestContext.Server.BoundHandle;
}
if (size == 0)
internal ResponseStreamAsyncResult(ResponseStream responseStream, BufferBuilder buffer, bool chunked,
CancellationTokenRegistration cancellationRegistration)
: this(responseStream, cancellationRegistration)
{
var boundHandle = _responseStream.RequestContext.Server.BoundHandle;
object[] objectsToPin;
if (buffer.TotalBytes == 0)
{
_dataChunks = null;
_overlapped = new SafeNativeOverlapped(boundHandle,
boundHandle.AllocateNativeOverlapped(IOCallback, this, null));
return;
}
else
_dataChunks = new HttpApi.HTTP_DATA_CHUNK[buffer.BufferCount + (chunked ? 2 : 0)];
objectsToPin = new object[_dataChunks.Length + 1];
objectsToPin[0] = _dataChunks;
var currentChunk = 0;
var currentPin = 1;
var chunkHeaderBuffer = new ArraySegment<byte>();
if (chunked)
{
_dataChunks = new UnsafeNclNativeMethods.HttpApi.HTTP_DATA_CHUNK[chunked ? 3 : 1];
object[] objectsToPin = new object[1 + _dataChunks.Length];
objectsToPin[_dataChunks.Length] = _dataChunks;
int chunkHeaderOffset = 0;
byte[] chunkHeaderBuffer = null;
if (chunked)
{
chunkHeaderBuffer = GetChunkHeader(size, out chunkHeaderOffset);
_dataChunks[0] = new UnsafeNclNativeMethods.HttpApi.HTTP_DATA_CHUNK();
_dataChunks[0].DataChunkType = UnsafeNclNativeMethods.HttpApi.HTTP_DATA_CHUNK_TYPE.HttpDataChunkFromMemory;
_dataChunks[0].fromMemory.BufferLength = (uint)(chunkHeaderBuffer.Length - chunkHeaderOffset);
objectsToPin[0] = chunkHeaderBuffer;
_dataChunks[1] = new UnsafeNclNativeMethods.HttpApi.HTTP_DATA_CHUNK();
_dataChunks[1].DataChunkType = UnsafeNclNativeMethods.HttpApi.HTTP_DATA_CHUNK_TYPE.HttpDataChunkFromMemory;
_dataChunks[1].fromMemory.BufferLength = (uint)size;
objectsToPin[1] = buffer;
_dataChunks[2] = new UnsafeNclNativeMethods.HttpApi.HTTP_DATA_CHUNK();
_dataChunks[2].DataChunkType = UnsafeNclNativeMethods.HttpApi.HTTP_DATA_CHUNK_TYPE.HttpDataChunkFromMemory;
_dataChunks[2].fromMemory.BufferLength = (uint)CRLF.Length;
objectsToPin[2] = CRLF;
}
else
{
_dataChunks[0] = new UnsafeNclNativeMethods.HttpApi.HTTP_DATA_CHUNK();
_dataChunks[0].DataChunkType = UnsafeNclNativeMethods.HttpApi.HTTP_DATA_CHUNK_TYPE.HttpDataChunkFromMemory;
_dataChunks[0].fromMemory.BufferLength = (uint)size;
objectsToPin[0] = buffer;
}
// This call will pin needed memory
_overlapped = new SafeNativeOverlapped(boundHandle,
boundHandle.AllocateNativeOverlapped(IOCallback, this, objectsToPin));
if (chunked)
{
_dataChunks[0].fromMemory.pBuffer = Marshal.UnsafeAddrOfPinnedArrayElement(chunkHeaderBuffer, chunkHeaderOffset);
_dataChunks[1].fromMemory.pBuffer = Marshal.UnsafeAddrOfPinnedArrayElement(buffer, offset);
_dataChunks[2].fromMemory.pBuffer = Marshal.UnsafeAddrOfPinnedArrayElement(CRLF, 0);
}
else
{
_dataChunks[0].fromMemory.pBuffer = Marshal.UnsafeAddrOfPinnedArrayElement(buffer, offset);
}
chunkHeaderBuffer = Helpers.GetChunkHeader(buffer.TotalBytes);
SetDataChunk(_dataChunks, ref currentChunk, objectsToPin, ref currentPin, chunkHeaderBuffer);
}
foreach (var segment in buffer.Buffers)
{
SetDataChunk(_dataChunks, ref currentChunk, objectsToPin, ref currentPin, segment);
}
if (chunked)
{
SetDataChunk(_dataChunks, ref currentChunk, objectsToPin, ref currentPin, new ArraySegment<byte>(Helpers.CRLF));
}
// This call will pin needed memory
_overlapped = new SafeNativeOverlapped(boundHandle,
boundHandle.AllocateNativeOverlapped(IOCallback, this, objectsToPin));
currentChunk = 0;
if (chunked)
{
_dataChunks[currentChunk].fromMemory.pBuffer = Marshal.UnsafeAddrOfPinnedArrayElement(chunkHeaderBuffer.Array, chunkHeaderBuffer.Offset);
currentChunk++;
}
foreach (var segment in buffer.Buffers)
{
_dataChunks[currentChunk].fromMemory.pBuffer = Marshal.UnsafeAddrOfPinnedArrayElement(segment.Array, segment.Offset);
currentChunk++;
}
if (chunked)
{
_dataChunks[currentChunk].fromMemory.pBuffer = Marshal.UnsafeAddrOfPinnedArrayElement(Helpers.CRLF, 0);
currentChunk++;
}
// We've captured a reference to all the buffers, clear the buffer so that it can be used to queue overlapped writes.
buffer.Clear();
}
internal ResponseStreamAsyncResult(ResponseStream responseStream, object userState, AsyncCallback callback,
string fileName, long offset, long? size, bool chunked, bool sentHeaders,
CancellationTokenRegistration cancellationRegistration)
: this(responseStream, userState, callback)
internal ResponseStreamAsyncResult(ResponseStream responseStream, string fileName, long offset,
long? count, bool chunked, CancellationTokenRegistration cancellationRegistration)
: this(responseStream, cancellationRegistration)
{
_sentHeaders = sentHeaders;
_cancellationRegistration = cancellationRegistration;
var boundHandle = ResponseStream.RequestContext.Server.BoundHandle;
var boundHandle = responseStream.RequestContext.Server.BoundHandle;
int bufferSize = 1024 * 64; // TODO: Validate buffer size choice.
#if DNXCORE50
@ -153,13 +133,13 @@ namespace Microsoft.Net.Http.Server
_fileStream.Dispose();
throw new ArgumentOutOfRangeException("offset", offset, string.Empty);
}
if (size.HasValue && (size < 0 || size > length - offset))
if (count.HasValue && (count < 0 || count > length - offset))
{
_fileStream.Dispose();
throw new ArgumentOutOfRangeException("size", size, string.Empty);
throw new ArgumentOutOfRangeException("count", count, string.Empty);
}
if (size == 0 || (!size.HasValue && _fileStream.Length == 0))
if (count == 0 || (!count.HasValue && _fileStream.Length == 0))
{
_dataChunks = null;
_overlapped = new SafeNativeOverlapped(boundHandle,
@ -167,42 +147,34 @@ namespace Microsoft.Net.Http.Server
}
else
{
_dataChunks = new UnsafeNclNativeMethods.HttpApi.HTTP_DATA_CHUNK[chunked ? 3 : 1];
_dataChunks = new HttpApi.HTTP_DATA_CHUNK[chunked ? 3 : 1];
object[] objectsToPin = new object[_dataChunks.Length];
objectsToPin[_dataChunks.Length - 1] = _dataChunks;
int chunkHeaderOffset = 0;
byte[] chunkHeaderBuffer = null;
var chunkHeaderBuffer = new ArraySegment<byte>();
if (chunked)
{
chunkHeaderBuffer = GetChunkHeader((int)(size ?? _fileStream.Length - offset), out chunkHeaderOffset);
chunkHeaderBuffer = Helpers.GetChunkHeader((int)(count ?? _fileStream.Length - offset));
_dataChunks[0].DataChunkType = HttpApi.HTTP_DATA_CHUNK_TYPE.HttpDataChunkFromMemory;
_dataChunks[0].fromMemory.BufferLength = (uint)chunkHeaderBuffer.Count;
objectsToPin[0] = chunkHeaderBuffer.Array;
_dataChunks[0] = new UnsafeNclNativeMethods.HttpApi.HTTP_DATA_CHUNK();
_dataChunks[0].DataChunkType = UnsafeNclNativeMethods.HttpApi.HTTP_DATA_CHUNK_TYPE.HttpDataChunkFromMemory;
_dataChunks[0].fromMemory.BufferLength = (uint)(chunkHeaderBuffer.Length - chunkHeaderOffset);
objectsToPin[0] = chunkHeaderBuffer;
_dataChunks[1] = new UnsafeNclNativeMethods.HttpApi.HTTP_DATA_CHUNK();
_dataChunks[1].DataChunkType = UnsafeNclNativeMethods.HttpApi.HTTP_DATA_CHUNK_TYPE.HttpDataChunkFromFileHandle;
_dataChunks[1].DataChunkType = HttpApi.HTTP_DATA_CHUNK_TYPE.HttpDataChunkFromFileHandle;
_dataChunks[1].fromFile.offset = (ulong)offset;
_dataChunks[1].fromFile.count = (ulong)(size ?? -1);
_dataChunks[1].fromFile.count = (ulong)(count ?? -1);
_dataChunks[1].fromFile.fileHandle = _fileStream.SafeFileHandle.DangerousGetHandle();
// Nothing to pin for the file handle.
_dataChunks[2] = new UnsafeNclNativeMethods.HttpApi.HTTP_DATA_CHUNK();
_dataChunks[2].DataChunkType = UnsafeNclNativeMethods.HttpApi.HTTP_DATA_CHUNK_TYPE.HttpDataChunkFromMemory;
_dataChunks[2].fromMemory.BufferLength = (uint)CRLF.Length;
objectsToPin[1] = CRLF;
_dataChunks[2].DataChunkType = HttpApi.HTTP_DATA_CHUNK_TYPE.HttpDataChunkFromMemory;
_dataChunks[2].fromMemory.BufferLength = (uint)Helpers.CRLF.Length;
objectsToPin[1] = Helpers.CRLF;
}
else
{
_dataChunks[0] = new UnsafeNclNativeMethods.HttpApi.HTTP_DATA_CHUNK();
_dataChunks[0].DataChunkType = UnsafeNclNativeMethods.HttpApi.HTTP_DATA_CHUNK_TYPE.HttpDataChunkFromFileHandle;
_dataChunks[0].DataChunkType = HttpApi.HTTP_DATA_CHUNK_TYPE.HttpDataChunkFromFileHandle;
_dataChunks[0].fromFile.offset = (ulong)offset;
_dataChunks[0].fromFile.count = (ulong)(size ?? -1);
_dataChunks[0].fromFile.count = (ulong)(count ?? -1);
_dataChunks[0].fromFile.fileHandle = _fileStream.SafeFileHandle.DangerousGetHandle();
}
@ -212,15 +184,21 @@ namespace Microsoft.Net.Http.Server
if (chunked)
{
_dataChunks[0].fromMemory.pBuffer = Marshal.UnsafeAddrOfPinnedArrayElement(chunkHeaderBuffer, chunkHeaderOffset);
_dataChunks[2].fromMemory.pBuffer = Marshal.UnsafeAddrOfPinnedArrayElement(CRLF, 0);
// These must be set after pinning with Overlapped.
_dataChunks[0].fromMemory.pBuffer = Marshal.UnsafeAddrOfPinnedArrayElement(chunkHeaderBuffer.Array, chunkHeaderBuffer.Offset);
_dataChunks[2].fromMemory.pBuffer = Marshal.UnsafeAddrOfPinnedArrayElement(Helpers.CRLF, 0);
}
}
}
internal ResponseStream ResponseStream
private static void SetDataChunk(HttpApi.HTTP_DATA_CHUNK[] chunks, ref int chunkIndex, object[] objectsToPin, ref int pinIndex, ArraySegment<byte> segment)
{
get { return _responseStream; }
objectsToPin[pinIndex] = segment.Array;
pinIndex++;
chunks[chunkIndex].DataChunkType = HttpApi.HTTP_DATA_CHUNK_TYPE.HttpDataChunkFromMemory;
// The address is not set until after we pin it with Overlapped
chunks[chunkIndex].fromMemory.BufferLength = (uint)segment.Count;
chunkIndex++;
}
internal SafeNativeOverlapped NativeOverlapped
@ -254,7 +232,7 @@ namespace Microsoft.Net.Http.Server
}
}
internal UnsafeNclNativeMethods.HttpApi.HTTP_DATA_CHUNK* DataChunks
internal HttpApi.HTTP_DATA_CHUNK* DataChunks
{
get
{
@ -264,7 +242,7 @@ namespace Microsoft.Net.Http.Server
}
else
{
return (UnsafeNclNativeMethods.HttpApi.HTTP_DATA_CHUNK*)(Marshal.UnsafeAddrOfPinnedArrayElement(_dataChunks, 0));
return (HttpApi.HTTP_DATA_CHUNK*)(Marshal.UnsafeAddrOfPinnedArrayElement(_dataChunks, 0));
}
}
}
@ -326,111 +304,17 @@ namespace Microsoft.Net.Http.Server
internal void Complete()
{
if (_tcs.TrySetResult(null) && _callback != null)
{
try
{
_callback(this);
}
catch (Exception)
{
// TODO: Exception handling? This may be an IO callback thread and throwing here could crash the app.
// TODO: Log
}
}
_tcs.TrySetResult(null);
Dispose();
}
internal void Fail(Exception ex)
{
if (_tcs.TrySetException(ex) && _callback != null)
{
try
{
_callback(this);
}
catch (Exception)
{
// TODO: Exception handling? This may be an IO callback thread and throwing here could crash the app.
}
}
_tcs.TrySetException(ex);
Dispose();
_responseStream.Abort();
}
/*++
GetChunkHeader
A private utility routine to convert an integer to a chunk header,
which is an ASCII hex number followed by a CRLF. The header is returned
as a byte array.
Input:
size - Chunk size to be encoded
offset - Out parameter where we store offset into buffer.
Returns:
A byte array with the header in int.
--*/
private static byte[] GetChunkHeader(int size, out int offset)
{
uint mask = 0xf0000000;
byte[] header = new byte[10];
int i;
offset = -1;
// Loop through the size, looking at each nibble. If it's not 0
// convert it to hex. Save the index of the first non-zero
// byte.
for (i = 0; i < 8; i++, size <<= 4)
{
// offset == -1 means that we haven't found a non-zero nibble
// yet. If we haven't found one, and the current one is zero,
// don't do anything.
if (offset == -1)
{
if ((size & mask) == 0)
{
continue;
}
}
// Either we have a non-zero nibble or we're no longer skipping
// leading zeros. Convert this nibble to ASCII and save it.
uint temp = (uint)size >> 28;
if (temp < 10)
{
header[i] = (byte)(temp + '0');
}
else
{
header[i] = (byte)((temp - 10) + 'A');
}
// If we haven't found a non-zero nibble yet, we've found one
// now, so remember that.
if (offset == -1)
{
offset = i;
}
}
header[8] = (byte)'\r';
header[9] = (byte)'\n';
return header;
}
public object AsyncState
{
get { return _tcs.Task.AsyncState; }

Просмотреть файл

@ -91,6 +91,8 @@ namespace Microsoft.Net.Http.Server
// The native request queue
private long? _requestQueueLength;
private bool _bufferResponses = true;
public WebListener()
: this(null)
{
@ -134,6 +136,12 @@ namespace Microsoft.Net.Http.Server
get { return _urlPrefixes; }
}
public bool BufferResponses
{
get { return _bufferResponses; }
set { _bufferResponses = value; }
}
internal SafeHandle RequestQueueHandle
{
get

Просмотреть файл

@ -13,5 +13,8 @@
<PropertyGroup>
<SchemaVersion>2.0</SchemaVersion>
</PropertyGroup>
<ItemGroup>
<Service Include="{82a7f48d-3b50-4b1e-b82e-3ada8210c358}" />
</ItemGroup>
<Import Project="$(VSToolsPath)\DNX\Microsoft.DNX.targets" Condition="'$(VSToolsPath)' != ''" />
</Project>

Просмотреть файл

@ -71,6 +71,7 @@ namespace Microsoft.AspNet.Server.WebListener
{
var httpContext = new DefaultHttpContext((IFeatureCollection)env);
await httpContext.Response.WriteAsync("Hello World");
await httpContext.Response.Body.FlushAsync();
try
{
var opaqueFeature = httpContext.GetFeature<IHttpUpgradeFeature>();
@ -171,8 +172,8 @@ namespace Microsoft.AspNet.Server.WebListener
using (Stream stream = await SendOpaqueRequestAsync(method, address, extraHeader))
{
byte[] data = new byte[100];
stream.WriteAsync(data, 0, 49).Wait();
int read = stream.ReadAsync(data, 0, data.Length).Result;
await stream.WriteAsync(data, 0, 49);
int read = await stream.ReadAsync(data, 0, data.Length);
Assert.Equal(49, read);
}
}

Просмотреть файл

@ -20,6 +20,7 @@ using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNet.FeatureModel;
@ -31,7 +32,7 @@ namespace Microsoft.AspNet.Server.WebListener
public class ResponseBodyTests
{
[Fact]
public async Task ResponseBody_WriteNoHeaders_DefaultsToChunked()
public async Task ResponseBody_WriteNoHeaders_BuffersAndSetsContentLength()
{
string address;
using (Utilities.CreateHttpServer(out address, env =>
@ -45,24 +46,22 @@ namespace Microsoft.AspNet.Server.WebListener
Assert.Equal(200, (int)response.StatusCode);
Assert.Equal(new Version(1, 1), response.Version);
IEnumerable<string> ignored;
Assert.False(response.Content.Headers.TryGetValues("content-length", out ignored), "Content-Length");
Assert.True(response.Headers.TransferEncodingChunked.Value, "Chunked");
Assert.True(response.Content.Headers.TryGetValues("content-length", out ignored), "Content-Length");
Assert.False(response.Headers.TransferEncodingChunked.HasValue, "Chunked");
Assert.Equal(new byte[20], await response.Content.ReadAsByteArrayAsync());
}
}
[Fact]
public async Task ResponseBody_WriteChunked_Chunked()
public async Task ResponseBody_WriteNoHeadersAndFlush_DefaultsToChunked()
{
string address;
using (Utilities.CreateHttpServer(out address, env =>
using (Utilities.CreateHttpServer(out address, async env =>
{
var httpContext = new DefaultHttpContext((IFeatureCollection)env);
httpContext.Request.Headers["transfeR-Encoding"] = " CHunked ";
Stream stream = httpContext.Response.Body;
stream.EndWrite(stream.BeginWrite(new byte[10], 0, 10, null, null));
stream.Write(new byte[10], 0, 10);
return stream.WriteAsync(new byte[10], 0, 10);
httpContext.Response.Body.Write(new byte[10], 0, 10);
await httpContext.Response.Body.WriteAsync(new byte[10], 0, 10);
await httpContext.Response.Body.FlushAsync();
}))
{
HttpResponseMessage response = await SendRequestAsync(address);
@ -71,7 +70,30 @@ namespace Microsoft.AspNet.Server.WebListener
IEnumerable<string> ignored;
Assert.False(response.Content.Headers.TryGetValues("content-length", out ignored), "Content-Length");
Assert.True(response.Headers.TransferEncodingChunked.Value, "Chunked");
Assert.Equal(new byte[30], await response.Content.ReadAsByteArrayAsync());
Assert.Equal(new byte[20], await response.Content.ReadAsByteArrayAsync());
}
}
[Fact]
public async Task ResponseBody_WriteChunked_ManuallyChunked()
{
string address;
using (Utilities.CreateHttpServer(out address, async env =>
{
var httpContext = new DefaultHttpContext((IFeatureCollection)env);
httpContext.Response.Headers["transfeR-Encoding"] = " CHunked ";
Stream stream = httpContext.Response.Body;
var responseBytes = Encoding.ASCII.GetBytes("10\r\nManually Chunked\r\n0\r\n\r\n");
await stream.WriteAsync(responseBytes, 0, responseBytes.Length);
}))
{
HttpResponseMessage response = await SendRequestAsync(address);
Assert.Equal(200, (int)response.StatusCode);
Assert.Equal(new Version(1, 1), response.Version);
IEnumerable<string> ignored;
Assert.False(response.Content.Headers.TryGetValues("content-length", out ignored), "Content-Length");
Assert.True(response.Headers.TransferEncodingChunked.Value, "Chunked");
Assert.Equal("Manually Chunked", await response.Content.ReadAsStringAsync());
}
}
@ -132,7 +154,7 @@ namespace Microsoft.AspNet.Server.WebListener
}
[Fact]
public void ResponseBody_WriteContentLengthTooMuchWritten_Throws()
public async Task ResponseBody_WriteContentLengthTooMuchWritten_Throws()
{
string address;
using (Utilities.CreateHttpServer(out address, env =>
@ -144,7 +166,8 @@ namespace Microsoft.AspNet.Server.WebListener
return Task.FromResult(0);
}))
{
Assert.Throws<AggregateException>(() => SendRequestAsync(address).Result);
var response = await SendRequestAsync(address);
Assert.Equal(500, (int)response.StatusCode);
}
}

Просмотреть файл

@ -84,7 +84,6 @@ namespace Microsoft.AspNet.Server.WebListener
{
HttpResponseMessage response = await SendRequestAsync(address);
Assert.Equal(200, (int)response.StatusCode);
Assert.True(response.Headers.TransferEncodingChunked.Value, "Chunked");
Assert.True(upgradeThrew.Value);
}
}

Просмотреть файл

@ -31,7 +31,6 @@ namespace Microsoft.Net.Http.Server
context.Dispose();
HttpResponseMessage response = await clientTask;
Assert.Equal(200, (int)response.StatusCode);
Assert.True(response.Headers.TransferEncodingChunked.Value, "Chunked");
Assert.Equal("Hello World", await response.Content.ReadAsStringAsync());
}
}

Просмотреть файл

@ -15,7 +15,7 @@ namespace Microsoft.Net.Http.Server
public class ResponseBodyTests
{
[Fact]
public async Task ResponseBody_WriteNoHeaders_DefaultsToChunked()
public async Task ResponseBody_BufferWriteNoHeaders_DefaultsToContentLength()
{
string address;
using (var server = Utilities.CreateHttpServer(out address))
@ -23,6 +23,31 @@ namespace Microsoft.Net.Http.Server
Task<HttpResponseMessage> responseTask = SendRequestAsync(address);
var context = await server.GetContextAsync();
context.Response.ShouldBuffer = true;
context.Response.Body.Write(new byte[10], 0, 10);
await context.Response.Body.WriteAsync(new byte[10], 0, 10);
context.Dispose();
HttpResponseMessage response = await responseTask;
Assert.Equal(200, (int)response.StatusCode);
Assert.Equal(new Version(1, 1), response.Version);
IEnumerable<string> ignored;
Assert.True(response.Content.Headers.TryGetValues("content-length", out ignored), "Content-Length");
Assert.False(response.Headers.TransferEncodingChunked.HasValue, "Chunked");
Assert.Equal(new byte[20], await response.Content.ReadAsByteArrayAsync());
}
}
[Fact]
public async Task ResponseBody_NoBufferWriteNoHeaders_DefaultsToChunked()
{
string address;
using (var server = Utilities.CreateHttpServer(out address))
{
Task<HttpResponseMessage> responseTask = SendRequestAsync(address);
var context = await server.GetContextAsync();
context.Response.ShouldBuffer = false;
context.Response.Body.Write(new byte[10], 0, 10);
await context.Response.Body.WriteAsync(new byte[10], 0, 10);
context.Dispose();
@ -37,6 +62,29 @@ namespace Microsoft.Net.Http.Server
}
}
[Fact]
public async Task ResponseBody_FlushThenBuffer_DefaultsToChunkedAndTerminates()
{
string address;
using (var server = Utilities.CreateHttpServer(out address))
{
Task<HttpResponseMessage> responseTask = SendRequestAsync(address);
var context = await server.GetContextAsync();
context.Response.Body.Write(new byte[10], 0, 10);
context.Response.Body.Flush();
await context.Response.Body.WriteAsync(new byte[10], 0, 10);
context.Dispose();
HttpResponseMessage response = await responseTask;
Assert.Equal(200, (int)response.StatusCode);
IEnumerable<string> contentLength;
Assert.False(response.Content.Headers.TryGetValues("content-length", out contentLength), "Content-Length");
Assert.True(response.Headers.TransferEncodingChunked.HasValue);
Assert.Equal(20, (await response.Content.ReadAsByteArrayAsync()).Length);
}
}
[Fact]
public async Task ResponseBody_WriteChunked_ManuallyChunked()
{
@ -111,7 +159,7 @@ namespace Microsoft.Net.Http.Server
}
[Fact]
public async Task ResponseBody_WriteContentLengthNotEnoughWritten_Throws()
public async Task ResponseBody_WriteContentLengthNotEnoughWritten_Aborts()
{
string address;
using (var server = Utilities.CreateHttpServer(out address))
@ -123,6 +171,12 @@ namespace Microsoft.Net.Http.Server
context.Response.Body.Write(new byte[5], 0, 5);
context.Dispose();
// HttpClient retries the request because it didn't get a response.
context = await server.GetContextAsync();
context.Response.Headers["Content-lenGth"] = " 20 ";
context.Response.Body.Write(new byte[5], 0, 5);
context.Dispose();
await Assert.ThrowsAsync<HttpRequestException>(() => responseTask);
}
}
@ -141,6 +195,13 @@ namespace Microsoft.Net.Http.Server
Assert.Throws<InvalidOperationException>(() => context.Response.Body.Write(new byte[6], 0, 6));
context.Dispose();
// HttpClient retries the request because it didn't get a response.
context = await server.GetContextAsync();
context.Response.Headers["Content-lenGth"] = " 10 ";
context.Response.Body.Write(new byte[5], 0, 5);
Assert.Throws<InvalidOperationException>(() => context.Response.Body.Write(new byte[6], 0, 6));
context.Dispose();
await Assert.ThrowsAsync<HttpRequestException>(() => responseTask);
}
}
@ -170,6 +231,92 @@ namespace Microsoft.Net.Http.Server
}
}
[Fact]
public async Task ResponseBody_WriteZeroCount_StartsResponse()
{
string address;
using (var server = Utilities.CreateHttpServer(out address))
{
Task<HttpResponseMessage> responseTask = SendRequestAsync(address);
var context = await server.GetContextAsync();
context.Response.Body.Write(new byte[10], 0, 0);
Assert.True(context.Response.HasStarted);
await context.Response.Body.WriteAsync(new byte[10], 0, 0);
context.Dispose();
HttpResponseMessage response = await responseTask;
Assert.Equal(200, (int)response.StatusCode);
Assert.Equal(new Version(1, 1), response.Version);
IEnumerable<string> ignored;
Assert.True(response.Content.Headers.TryGetValues("content-length", out ignored), "Content-Length");
Assert.False(response.Headers.TransferEncodingChunked.HasValue, "Chunked");
Assert.Equal(new byte[0], await response.Content.ReadAsByteArrayAsync());
}
}
[Fact]
public async Task ResponseBody_WriteMoreThanBufferLimitBufferWithNoHeaders_DefaultsToChunkedAndFlushes()
{
string address;
using (var server = Utilities.CreateHttpServer(out address))
{
Task<HttpResponseMessage> responseTask = SendRequestAsync(address);
var context = await server.GetContextAsync();
context.Response.ShouldBuffer = true;
for (int i = 0; i < 4; i++)
{
context.Response.Body.Write(new byte[1020], 0, 1020);
Assert.True(context.Response.HasStarted);
Assert.False(context.Response.HasStartedSending);
}
context.Response.Body.Write(new byte[1020], 0, 1020);
Assert.True(context.Response.HasStartedSending);
context.Response.Body.Write(new byte[1020], 0, 1020);
context.Dispose();
HttpResponseMessage response = await responseTask;
Assert.Equal(200, (int)response.StatusCode);
Assert.Equal(new Version(1, 1), response.Version);
IEnumerable<string> ignored;
Assert.False(response.Content.Headers.TryGetValues("content-length", out ignored), "Content-Length");
Assert.True(response.Headers.TransferEncodingChunked.Value, "Chunked");
Assert.Equal(new byte[1020*6], await response.Content.ReadAsByteArrayAsync());
}
}
[Fact]
public async Task ResponseBody_WriteAsyncMoreThanBufferLimitBufferWithNoHeaders_DefaultsToChunkedAndFlushes()
{
string address;
using (var server = Utilities.CreateHttpServer(out address))
{
Task<HttpResponseMessage> responseTask = SendRequestAsync(address);
var context = await server.GetContextAsync();
context.Response.ShouldBuffer = true;
for (int i = 0; i < 4; i++)
{
await context.Response.Body.WriteAsync(new byte[1020], 0, 1020);
Assert.True(context.Response.HasStarted);
Assert.False(context.Response.HasStartedSending);
}
await context.Response.Body.WriteAsync(new byte[1020], 0, 1020);
Assert.True(context.Response.HasStartedSending);
await context.Response.Body.WriteAsync(new byte[1020], 0, 1020);
context.Dispose();
HttpResponseMessage response = await responseTask;
Assert.Equal(200, (int)response.StatusCode);
Assert.Equal(new Version(1, 1), response.Version);
IEnumerable<string> ignored;
Assert.False(response.Content.Headers.TryGetValues("content-length", out ignored), "Content-Length");
Assert.True(response.Headers.TransferEncodingChunked.Value, "Chunked");
Assert.Equal(new byte[1020 * 6], await response.Content.ReadAsByteArrayAsync());
}
}
[Fact]
public async Task ResponseBody_WriteAsyncWithActiveCancellationToken_Success()
{

Просмотреть файл

@ -349,13 +349,13 @@ namespace Microsoft.Net.Http.Server
responseHeaders.SetValues("Custom1", "value1a", "value1b");
responseHeaders.SetValues("Custom2", "value2a, value2b");
var body = context.Response.Body;
Assert.False(context.Response.HeadersSent);
Assert.False(context.Response.HasStarted);
body.Flush();
Assert.True(context.Response.HeadersSent);
Assert.True(context.Response.HasStarted);
var ex = Assert.Throws<InvalidOperationException>(() => context.Response.StatusCode = 404);
Assert.Equal("Headers already sent.", ex.Message);
ex = Assert.Throws<InvalidOperationException>(() => responseHeaders.Add("Custom3", new string[] { "value3a, value3b", "value3c" }));
Assert.Equal("The response headers cannot be modified because they have already been sent.", ex.Message);
Assert.Equal("The response headers cannot be modified because the response has already started.", ex.Message);
context.Dispose();
@ -385,13 +385,13 @@ namespace Microsoft.Net.Http.Server
responseHeaders.SetValues("Custom1", "value1a", "value1b");
responseHeaders.SetValues("Custom2", "value2a, value2b");
var body = context.Response.Body;
Assert.False(context.Response.HeadersSent);
Assert.False(context.Response.HasStarted);
await body.FlushAsync();
Assert.True(context.Response.HeadersSent);
Assert.True(context.Response.HasStarted);
var ex = Assert.Throws<InvalidOperationException>(() => context.Response.StatusCode = 404);
Assert.Equal("Headers already sent.", ex.Message);
ex = Assert.Throws<InvalidOperationException>(() => responseHeaders.Add("Custom3", new string[] { "value3a, value3b", "value3c" }));
Assert.Equal("The response headers cannot be modified because they have already been sent.", ex.Message);
Assert.Equal("The response headers cannot be modified because the response has already started.", ex.Message);
context.Dispose();

Просмотреть файл

@ -202,6 +202,34 @@ namespace Microsoft.Net.Http.Server
}
}
[Fact]
public async Task ResponseSendFile_EmptyFileCountUnspecified_SetsChunkedAndFlushesHeaders()
{
var emptyFilePath = Path.Combine(Environment.CurrentDirectory, "zz_" + Guid.NewGuid().ToString() + "EmptyTestFile.txt");
var emptyFile = File.Create(emptyFilePath, 1024);
emptyFile.Close();
string address;
using (var server = Utilities.CreateHttpServer(out address))
{
Task<HttpResponseMessage> responseTask = SendRequestAsync(address);
var context = await server.GetContextAsync();
await context.Response.SendFileAsync(emptyFilePath, 0, null, CancellationToken.None);
Assert.True(context.Response.HasStartedSending);
await context.Response.Body.WriteAsync(new byte[10], 0, 10, CancellationToken.None);
context.Dispose();
File.Delete(emptyFilePath);
HttpResponseMessage response = await responseTask;
Assert.Equal(200, (int)response.StatusCode);
IEnumerable<string> contentLength;
Assert.False(response.Content.Headers.TryGetValues("content-length", out contentLength), "Content-Length");
Assert.True(response.Headers.TransferEncodingChunked.HasValue);
Assert.Equal(10, (await response.Content.ReadAsByteArrayAsync()).Length);
}
}
[Fact]
public async Task ResponseSendFile_ContentLength_PassedThrough()
{

Просмотреть файл

@ -30,7 +30,6 @@ namespace Microsoft.Net.Http.Server
context.Dispose();
HttpResponseMessage response = await clientTask;
Assert.Equal(200, (int)response.StatusCode);
Assert.True(response.Headers.TransferEncodingChunked.Value, "Chunked");
Assert.Equal("Hello World", await response.Content.ReadAsStringAsync());
}
}