aspnetcore/src/Microsoft.Net.Http.Server/RequestProcessing/RequestStream.cs

450 lines
16 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="_HttpRequestStream.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
// ------------------------------------------------------------------------------
using System;
using System.IO;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
namespace Microsoft.Net.Http.Server
{
internal class RequestStream : Stream
{
private const int MaxReadSize = 0x20000; // http.sys recommends we limit reads to 128k
private RequestContext _requestContext;
private uint _dataChunkOffset;
private int _dataChunkIndex;
private bool _closed;
internal RequestStream(RequestContext httpContext)
{
_requestContext = httpContext;
}
public override bool CanSeek
{
get
{
return false;
}
}
public override bool CanWrite
{
get
{
return false;
}
}
public override bool CanRead
{
get
{
return true;
}
}
public override long Length
{
get
{
throw new NotSupportedException(Resources.Exception_NoSeek);
}
}
public override long Position
{
get
{
throw new NotSupportedException(Resources.Exception_NoSeek);
}
set
{
throw new NotSupportedException(Resources.Exception_NoSeek);
}
}
public override long Seek(long offset, SeekOrigin origin)
{
throw new NotSupportedException(Resources.Exception_NoSeek);
}
public override void SetLength(long value)
{
throw new NotSupportedException(Resources.Exception_NoSeek);
}
public override void Flush()
{
throw new InvalidOperationException(Resources.Exception_ReadOnlyStream);
}
public override Task FlushAsync(CancellationToken cancellationToken)
{
throw new InvalidOperationException(Resources.Exception_ReadOnlyStream);
}
private void ValidateReadBuffer(byte[] buffer, int offset, int size)
{
if (buffer == null)
{
throw new ArgumentNullException("buffer");
}
if (offset < 0 || offset > buffer.Length)
{
throw new ArgumentOutOfRangeException("offset", offset, string.Empty);
}
if (size <= 0 || size > buffer.Length - offset)
{
throw new ArgumentOutOfRangeException("size", size, string.Empty);
}
}
public override unsafe int Read([In, Out] byte[] buffer, int offset, int size)
{
ValidateReadBuffer(buffer, offset, size);
if (_closed)
{
return 0;
}
// TODO: Verbose log parameters
uint dataRead = 0;
if (_dataChunkIndex != -1)
{
dataRead = UnsafeNclNativeMethods.HttpApi.GetChunks(_requestContext.Request.RequestBuffer, _requestContext.Request.OriginalBlobAddress, ref _dataChunkIndex, ref _dataChunkOffset, buffer, offset, size);
}
if (_dataChunkIndex == -1 && dataRead < size)
{
uint statusCode = 0;
uint extraDataRead = 0;
offset += (int)dataRead;
size -= (int)dataRead;
// the http.sys team recommends that we limit the size to 128kb
if (size > MaxReadSize)
{
size = MaxReadSize;
}
fixed (byte* pBuffer = buffer)
{
// issue unmanaged blocking call
uint flags = 0;
statusCode =
UnsafeNclNativeMethods.HttpApi.HttpReceiveRequestEntityBody(
_requestContext.RequestQueueHandle,
_requestContext.RequestId,
flags,
(IntPtr)(pBuffer + offset),
(uint)size,
out extraDataRead,
SafeNativeOverlapped.Zero);
dataRead += extraDataRead;
}
if (statusCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS && statusCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_HANDLE_EOF)
{
Exception exception = new WebListenerException((int)statusCode);
LogHelper.LogException(_requestContext.Logger, "Read", exception);
throw exception;
}
UpdateAfterRead(statusCode, dataRead);
}
// TODO: Verbose log dump data read
return (int)dataRead;
}
internal void UpdateAfterRead(uint statusCode, uint dataRead)
{
if (statusCode == UnsafeNclNativeMethods.ErrorCodes.ERROR_HANDLE_EOF || dataRead == 0)
{
Dispose();
}
}
#if ASPNET50
public override unsafe IAsyncResult BeginRead(byte[] buffer, int offset, int size, AsyncCallback callback, object state)
#else
public unsafe IAsyncResult BeginRead(byte[] buffer, int offset, int size, AsyncCallback callback, object state)
#endif
{
ValidateReadBuffer(buffer, offset, size);
if (_closed)
{
RequestStreamAsyncResult result = new RequestStreamAsyncResult(this, state, callback);
result.Complete(0);
return result;
}
// TODO: Verbose log parameters
RequestStreamAsyncResult asyncResult = null;
uint dataRead = 0;
if (_dataChunkIndex != -1)
{
dataRead = UnsafeNclNativeMethods.HttpApi.GetChunks(_requestContext.Request.RequestBuffer, _requestContext.Request.OriginalBlobAddress, ref _dataChunkIndex, ref _dataChunkOffset, buffer, offset, size);
if (_dataChunkIndex != -1 && dataRead == size)
{
asyncResult = new RequestStreamAsyncResult(this, state, callback, buffer, offset, 0);
asyncResult.Complete((int)dataRead);
}
}
if (_dataChunkIndex == -1 && dataRead < size)
{
uint statusCode = 0;
offset += (int)dataRead;
size -= (int)dataRead;
// the http.sys team recommends that we limit the size to 128kb
if (size > MaxReadSize)
{
size = MaxReadSize;
}
asyncResult = new RequestStreamAsyncResult(this, state, callback, buffer, offset, dataRead);
uint bytesReturned;
try
{
fixed (byte* pBuffer = buffer)
{
uint flags = 0;
statusCode =
UnsafeNclNativeMethods.HttpApi.HttpReceiveRequestEntityBody(
_requestContext.RequestQueueHandle,
_requestContext.RequestId,
flags,
asyncResult.PinnedBuffer,
(uint)size,
out bytesReturned,
asyncResult.NativeOverlapped);
}
}
catch (Exception e)
{
LogHelper.LogException(_requestContext.Logger, "BeginRead", e);
asyncResult.Dispose();
throw;
}
if (statusCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS && statusCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_IO_PENDING)
{
asyncResult.Dispose();
if (statusCode == UnsafeNclNativeMethods.ErrorCodes.ERROR_HANDLE_EOF)
{
asyncResult = new RequestStreamAsyncResult(this, state, callback, dataRead);
asyncResult.Complete((int)bytesReturned);
}
else
{
Exception exception = new WebListenerException((int)statusCode);
LogHelper.LogException(_requestContext.Logger, "BeginRead", exception);
throw exception;
}
}
else if (statusCode == UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS &&
WebListener.SkipIOCPCallbackOnSuccess)
{
// IO operation completed synchronously - callback won't be called to signal completion.
asyncResult.IOCompleted(statusCode, bytesReturned);
}
}
return asyncResult;
}
#if ASPNET50
public override int EndRead(IAsyncResult asyncResult)
#else
public int EndRead(IAsyncResult asyncResult)
#endif
{
if (asyncResult == null)
{
throw new ArgumentNullException("asyncResult");
}
RequestStreamAsyncResult castedAsyncResult = asyncResult as RequestStreamAsyncResult;
if (castedAsyncResult == null || castedAsyncResult.RequestStream != this)
{
throw new ArgumentException(Resources.Exception_WrongIAsyncResult, "asyncResult");
}
if (castedAsyncResult.EndCalled)
{
throw new InvalidOperationException(Resources.Exception_EndCalledMultipleTimes);
}
castedAsyncResult.EndCalled = true;
// wait & then check for errors
// Throws on failure
int dataRead = castedAsyncResult.Task.Result;
// TODO: Verbose log #dataRead.
return dataRead;
}
public override unsafe Task<int> ReadAsync(byte[] buffer, int offset, int size, CancellationToken cancellationToken)
{
ValidateReadBuffer(buffer, offset, size);
if (_closed)
{
return Task.FromResult<int>(0);
}
if (cancellationToken.IsCancellationRequested)
{
return Helpers.CanceledTask<int>();
}
// TODO: Verbose log parameters
RequestStreamAsyncResult asyncResult = null;
uint dataRead = 0;
if (_dataChunkIndex != -1)
{
dataRead = UnsafeNclNativeMethods.HttpApi.GetChunks(_requestContext.Request.RequestBuffer, _requestContext.Request.OriginalBlobAddress, ref _dataChunkIndex, ref _dataChunkOffset, buffer, offset, size);
if (_dataChunkIndex != -1 && dataRead == size)
{
UpdateAfterRead(UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS, dataRead);
// TODO: Verbose log #dataRead
return Task.FromResult<int>((int)dataRead);
}
}
if (_dataChunkIndex == -1 && dataRead < size)
{
uint statusCode = 0;
offset += (int)dataRead;
size -= (int)dataRead;
// the http.sys team recommends that we limit the size to 128kb
if (size > MaxReadSize)
{
size = MaxReadSize;
}
CancellationTokenRegistration cancellationRegistration;
if (cancellationToken.CanBeCanceled)
{
cancellationRegistration = cancellationToken.Register(RequestContext.AbortDelegate, _requestContext);
}
asyncResult = new RequestStreamAsyncResult(this, null, null, buffer, offset, dataRead, cancellationRegistration);
uint bytesReturned;
try
{
uint flags = 0;
statusCode =
UnsafeNclNativeMethods.HttpApi.HttpReceiveRequestEntityBody(
_requestContext.RequestQueueHandle,
_requestContext.RequestId,
flags,
asyncResult.PinnedBuffer,
(uint)size,
out bytesReturned,
asyncResult.NativeOverlapped);
}
catch (Exception e)
{
asyncResult.Dispose();
LogHelper.LogException(_requestContext.Logger, "ReadAsync", e);
throw;
}
if (statusCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS && statusCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_IO_PENDING)
{
asyncResult.Dispose();
if (statusCode == UnsafeNclNativeMethods.ErrorCodes.ERROR_HANDLE_EOF)
{
uint totalRead = dataRead + bytesReturned;
UpdateAfterRead(statusCode, totalRead);
// TODO: Verbose log totalRead
return Task.FromResult<int>((int)totalRead);
}
else
{
Exception exception = new WebListenerException((int)statusCode);
LogHelper.LogException(_requestContext.Logger, "ReadAsync", exception);
throw exception;
}
}
else if (statusCode == UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS &&
WebListener.SkipIOCPCallbackOnSuccess)
{
// IO operation completed synchronously - callback won't be called to signal completion.
asyncResult.Dispose();
uint totalRead = dataRead + bytesReturned;
UpdateAfterRead(statusCode, totalRead);
// TODO: Verbose log
return Task.FromResult<int>((int)totalRead);
}
}
return asyncResult.Task;
}
public override void Write(byte[] buffer, int offset, int size)
{
throw new InvalidOperationException(Resources.Exception_ReadOnlyStream);
}
#if ASPNET50
public override IAsyncResult BeginWrite(byte[] buffer, int offset, int size, AsyncCallback callback, object state)
#else
public IAsyncResult BeginWrite(byte[] buffer, int offset, int size, AsyncCallback callback, object state)
#endif
{
throw new InvalidOperationException(Resources.Exception_ReadOnlyStream);
}
#if ASPNET50
public override void EndWrite(IAsyncResult asyncResult)
#else
public void EndWrite(IAsyncResult asyncResult)
#endif
{
throw new InvalidOperationException(Resources.Exception_ReadOnlyStream);
}
protected override void Dispose(bool disposing)
{
try
{
_closed = true;
}
finally
{
base.Dispose(disposing);
}
}
}
}