468 lines
18 KiB
C#
468 lines
18 KiB
C#
// Copyright (c) Microsoft Open Technologies, Inc.
|
|
// All Rights Reserved
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR
|
|
// CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING
|
|
// WITHOUT LIMITATION ANY IMPLIED WARRANTIES OR CONDITIONS OF
|
|
// TITLE, FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABLITY OR
|
|
// NON-INFRINGEMENT.
|
|
// See the Apache 2 License for the specific language governing
|
|
// permissions and limitations under the License.
|
|
|
|
// -----------------------------------------------------------------------
|
|
// <copyright file="ResponseStreamAsyncResult.cs" company="Microsoft">
|
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
|
// </copyright>
|
|
// -----------------------------------------------------------------------
|
|
|
|
using System;
|
|
using System.Diagnostics.CodeAnalysis;
|
|
using System.IO;
|
|
using System.Runtime.InteropServices;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
|
|
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 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)
|
|
{
|
|
_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;
|
|
_cancellationRegistration = cancellationRegistration;
|
|
var boundHandle = _responseStream.RequestContext.Server.BoundHandle;
|
|
|
|
if (size == 0)
|
|
{
|
|
_dataChunks = null;
|
|
_overlapped = new SafeNativeOverlapped(boundHandle,
|
|
boundHandle.AllocateNativeOverlapped(IOCallback, this, null));
|
|
}
|
|
else
|
|
{
|
|
_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);
|
|
}
|
|
}
|
|
}
|
|
|
|
internal ResponseStreamAsyncResult(ResponseStream responseStream, object userState, AsyncCallback callback,
|
|
string fileName, long offset, long? size, bool chunked, bool sentHeaders,
|
|
CancellationTokenRegistration cancellationRegistration)
|
|
: this(responseStream, userState, callback)
|
|
{
|
|
_sentHeaders = sentHeaders;
|
|
_cancellationRegistration = cancellationRegistration;
|
|
var boundHandle = ResponseStream.RequestContext.Server.BoundHandle;
|
|
|
|
int bufferSize = 1024 * 64; // TODO: Validate buffer size choice.
|
|
#if DNXCORE50
|
|
_fileStream = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, bufferSize /*, useAsync: true*/); // Extremely expensive.
|
|
#else
|
|
// It's too expensive to validate anything before opening the file. Open the file and then check the lengths.
|
|
_fileStream = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, bufferSize,
|
|
FileOptions.Asynchronous | FileOptions.SequentialScan); // Extremely expensive.
|
|
#endif
|
|
long length = _fileStream.Length; // Expensive
|
|
if (offset < 0 || offset > length)
|
|
{
|
|
_fileStream.Dispose();
|
|
throw new ArgumentOutOfRangeException("offset", offset, string.Empty);
|
|
}
|
|
if (size.HasValue && (size < 0 || size > length - offset))
|
|
{
|
|
_fileStream.Dispose();
|
|
throw new ArgumentOutOfRangeException("size", size, string.Empty);
|
|
}
|
|
|
|
if (size == 0 || (!size.HasValue && _fileStream.Length == 0))
|
|
{
|
|
_dataChunks = null;
|
|
_overlapped = new SafeNativeOverlapped(boundHandle,
|
|
boundHandle.AllocateNativeOverlapped(IOCallback, this, null));
|
|
}
|
|
else
|
|
{
|
|
_dataChunks = new UnsafeNclNativeMethods.HttpApi.HTTP_DATA_CHUNK[chunked ? 3 : 1];
|
|
|
|
object[] objectsToPin = new object[_dataChunks.Length];
|
|
objectsToPin[_dataChunks.Length - 1] = _dataChunks;
|
|
|
|
int chunkHeaderOffset = 0;
|
|
byte[] chunkHeaderBuffer = null;
|
|
if (chunked)
|
|
{
|
|
chunkHeaderBuffer = GetChunkHeader((int)(size ?? _fileStream.Length - offset), 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.HttpDataChunkFromFileHandle;
|
|
_dataChunks[1].fromFile.offset = (ulong)offset;
|
|
_dataChunks[1].fromFile.count = (ulong)(size ?? -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;
|
|
}
|
|
else
|
|
{
|
|
_dataChunks[0] = new UnsafeNclNativeMethods.HttpApi.HTTP_DATA_CHUNK();
|
|
_dataChunks[0].DataChunkType = UnsafeNclNativeMethods.HttpApi.HTTP_DATA_CHUNK_TYPE.HttpDataChunkFromFileHandle;
|
|
_dataChunks[0].fromFile.offset = (ulong)offset;
|
|
_dataChunks[0].fromFile.count = (ulong)(size ?? -1);
|
|
_dataChunks[0].fromFile.fileHandle = _fileStream.SafeFileHandle.DangerousGetHandle();
|
|
}
|
|
|
|
// 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[2].fromMemory.pBuffer = Marshal.UnsafeAddrOfPinnedArrayElement(CRLF, 0);
|
|
}
|
|
}
|
|
}
|
|
|
|
internal ResponseStream ResponseStream
|
|
{
|
|
get { return _responseStream; }
|
|
}
|
|
|
|
internal SafeNativeOverlapped NativeOverlapped
|
|
{
|
|
get { return _overlapped; }
|
|
}
|
|
|
|
internal Task Task
|
|
{
|
|
get { return _tcs.Task; }
|
|
}
|
|
|
|
internal uint BytesSent
|
|
{
|
|
get { return _bytesSent; }
|
|
set { _bytesSent = value; }
|
|
}
|
|
|
|
internal ushort DataChunkCount
|
|
{
|
|
get
|
|
{
|
|
if (_dataChunks == null)
|
|
{
|
|
return 0;
|
|
}
|
|
else
|
|
{
|
|
return (ushort)_dataChunks.Length;
|
|
}
|
|
}
|
|
}
|
|
|
|
internal UnsafeNclNativeMethods.HttpApi.HTTP_DATA_CHUNK* DataChunks
|
|
{
|
|
get
|
|
{
|
|
if (_dataChunks == null)
|
|
{
|
|
return null;
|
|
}
|
|
else
|
|
{
|
|
return (UnsafeNclNativeMethods.HttpApi.HTTP_DATA_CHUNK*)(Marshal.UnsafeAddrOfPinnedArrayElement(_dataChunks, 0));
|
|
}
|
|
}
|
|
}
|
|
|
|
internal long FileLength
|
|
{
|
|
get { return _fileStream == null ? 0 : _fileStream.Length; }
|
|
}
|
|
|
|
internal bool EndCalled { get; set; }
|
|
|
|
internal void IOCompleted(uint errorCode)
|
|
{
|
|
IOCompleted(this, errorCode, BytesSent);
|
|
}
|
|
|
|
internal void IOCompleted(uint errorCode, uint numBytes)
|
|
{
|
|
IOCompleted(this, errorCode, numBytes);
|
|
}
|
|
|
|
[SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Redirecting to callback")]
|
|
private static void IOCompleted(ResponseStreamAsyncResult asyncResult, uint errorCode, uint numBytes)
|
|
{
|
|
try
|
|
{
|
|
if (errorCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS && errorCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_HANDLE_EOF)
|
|
{
|
|
asyncResult.Fail(new IOException(string.Empty, new WebListenerException((int)errorCode)));
|
|
}
|
|
else
|
|
{
|
|
if (asyncResult._dataChunks == null)
|
|
{
|
|
// TODO: Verbose log data written
|
|
}
|
|
else
|
|
{
|
|
// TODO: Verbose log
|
|
// for (int i = 0; i < asyncResult._dataChunks.Length; i++)
|
|
// {
|
|
// Logging.Dump(Logging.HttpListener, asyncResult, "Callback", (IntPtr)asyncResult._dataChunks[0].fromMemory.pBuffer, (int)asyncResult._dataChunks[0].fromMemory.BufferLength);
|
|
// }
|
|
}
|
|
asyncResult.Complete();
|
|
}
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
asyncResult.Fail(e);
|
|
}
|
|
}
|
|
|
|
private static unsafe void Callback(uint errorCode, uint numBytes, NativeOverlapped* nativeOverlapped)
|
|
{
|
|
var asyncResult = (ResponseStreamAsyncResult)ThreadPoolBoundHandle.GetNativeOverlappedState(nativeOverlapped);
|
|
IOCompleted(asyncResult, errorCode, numBytes);
|
|
}
|
|
|
|
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
|
|
}
|
|
}
|
|
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.
|
|
}
|
|
}
|
|
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; }
|
|
}
|
|
|
|
public WaitHandle AsyncWaitHandle
|
|
{
|
|
get { return ((IAsyncResult)_tcs.Task).AsyncWaitHandle; }
|
|
}
|
|
|
|
public bool CompletedSynchronously
|
|
{
|
|
get { return ((IAsyncResult)_tcs.Task).CompletedSynchronously; }
|
|
}
|
|
|
|
public bool IsCompleted
|
|
{
|
|
get { return _tcs.Task.IsCompleted; }
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
if (_overlapped != null)
|
|
{
|
|
_overlapped.Dispose();
|
|
}
|
|
if (_fileStream != null)
|
|
{
|
|
_fileStream.Dispose();
|
|
}
|
|
_cancellationRegistration.Dispose();
|
|
}
|
|
}
|
|
}
|