// 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 (c) Microsoft Corporation. All rights reserved. // // ------------------------------------------------------------------------------ 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 ReadAsync(byte[] buffer, int offset, int size, CancellationToken cancellationToken) { ValidateReadBuffer(buffer, offset, size); if (_closed) { return Task.FromResult(0); } if (cancellationToken.IsCancellationRequested) { return Helpers.CanceledTask(); } // 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)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)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)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); } } } }