private static void IOCompleted(ResponseStreamAsyncResult asyncResult, uint errorCode, uint numBytes)
        {
            var logger = asyncResult._responseStream.RequestContext.Logger;

            try
            {
                if (errorCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS && errorCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_HANDLE_EOF)
                {
                    if (asyncResult._cancellationToken.IsCancellationRequested)
                    {
                        LogHelper.LogDebug(logger, "FlushAsync.IOCompleted", $"Write cancelled with error code: {errorCode}");
                        asyncResult.Cancel(asyncResult._responseStream.ThrowWriteExceptions);
                    }
                    else if (asyncResult._responseStream.ThrowWriteExceptions)
                    {
                        var exception = new IOException(string.Empty, new HttpSysException((int)errorCode));
                        LogHelper.LogException(logger, "FlushAsync.IOCompleted", exception);
                        asyncResult.Fail(exception);
                    }
                    else
                    {
                        LogHelper.LogDebug(logger, "FlushAsync.IOCompleted", $"Ignored write exception: {errorCode}");
                        asyncResult.FailSilently();
                    }
                }
                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)
            {
                LogHelper.LogException(logger, "FlushAsync.IOCompleted", e);
                asyncResult.Fail(e);
            }
        }
Ejemplo n.º 2
0
        internal void RegisterPrefix(string uriPrefix, int contextId)
        {
            LogHelper.LogDebug(_logger, "Listening on prefix: " + uriPrefix);
            CheckDisposed();
            var statusCode = HttpApi.HttpAddUrlToUrlGroup(Id, uriPrefix, (ulong)contextId, 0);

            if (statusCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS)
            {
                if (statusCode == UnsafeNclNativeMethods.ErrorCodes.ERROR_ALREADY_EXISTS)
                {
                    throw new HttpSysException((int)statusCode, string.Format(Resources.Exception_PrefixAlreadyRegistered, uriPrefix));
                }
                else
                {
                    throw new HttpSysException((int)statusCode);
                }
            }
        }
Ejemplo n.º 3
0
        // The message pump.
        // When we start listening for the next request on one thread, we may need to be sure that the
        // completion continues on another thread as to not block the current request processing.
        // The awaits will manage stack depth for us.
        private async void ProcessRequestsWorker()
        {
            int workerIndex = Interlocked.Increment(ref _acceptorCounts);

            while (!Stopping && workerIndex <= _maxAccepts)
            {
                // Receive a request
                RequestContext requestContext;
                try
                {
                    requestContext = await Listener.AcceptAsync().SupressContext();
                }
                catch (Exception exception)
                {
                    Contract.Assert(Stopping);
                    if (Stopping)
                    {
                        LogHelper.LogDebug(_logger, "ListenForNextRequestAsync-Stopping", exception);
                    }
                    else
                    {
                        LogHelper.LogException(_logger, "ListenForNextRequestAsync", exception);
                    }
                    continue;
                }
                try
                {
                    Task ignored = Task.Factory.StartNew(_processRequest, requestContext);
                }
                catch (Exception ex)
                {
                    // Request processing failed to be queued in threadpool
                    // Log the error message, release throttle and move on
                    LogHelper.LogException(_logger, "ProcessRequestAsync", ex);
                }
            }
            Interlocked.Decrement(ref _acceptorCounts);
        }
Ejemplo n.º 4
0
        private unsafe CancellationToken CreateDisconnectToken(ulong connectionId)
        {
            LogHelper.LogDebug(_logger, "CreateDisconnectToken", "Registering connection for disconnect for connection ID: " + connectionId);

            // Create a nativeOverlapped callback so we can register for disconnect callback
            var cts         = new CancellationTokenSource();
            var returnToken = cts.Token;

            SafeNativeOverlapped nativeOverlapped = null;
            var boundHandle = _requestQueue.BoundHandle;

            nativeOverlapped = new SafeNativeOverlapped(boundHandle, boundHandle.AllocateNativeOverlapped(
                                                            (errorCode, numBytes, overlappedPtr) =>
            {
                LogHelper.LogDebug(_logger, "CreateDisconnectToken", "http.sys disconnect callback fired for connection ID: " + connectionId);

                // Free the overlapped
                nativeOverlapped.Dispose();

                // Pull the token out of the list and Cancel it.
                ConnectionCancellation token;
                _connectionCancellationTokens.TryRemove(connectionId, out token);
                try
                {
                    cts.Cancel();
                }
                catch (AggregateException exception)
                {
                    LogHelper.LogException(_logger, "CreateDisconnectToken Callback", exception);
                }
            },
                                                            null, null));

            uint statusCode;

            try
            {
                statusCode = HttpApi.HttpWaitForDisconnectEx(requestQueueHandle: _requestQueue.Handle,
                                                             connectionId: connectionId, reserved: 0, overlapped: nativeOverlapped);
            }
            catch (Win32Exception exception)
            {
                statusCode = (uint)exception.NativeErrorCode;
                LogHelper.LogException(_logger, "CreateDisconnectToken", exception);
            }

            if (statusCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_IO_PENDING &&
                statusCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS)
            {
                // We got an unknown result, assume the connection has been closed.
                nativeOverlapped.Dispose();
                ConnectionCancellation ignored;
                _connectionCancellationTokens.TryRemove(connectionId, out ignored);
                LogHelper.LogDebug(_logger, "HttpWaitForDisconnectEx", new Win32Exception((int)statusCode));
                cts.Cancel();
            }

            if (statusCode == UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS && HttpSysListener.SkipIOCPCallbackOnSuccess)
            {
                // IO operation completed synchronously - callback won't be called to signal completion
                nativeOverlapped.Dispose();
                ConnectionCancellation ignored;
                _connectionCancellationTokens.TryRemove(connectionId, out ignored);
                cts.Cancel();
            }

            return(returnToken);
        }
Ejemplo n.º 5
0
        internal unsafe Task SendFileAsyncCore(string fileName, long offset, long?count, CancellationToken cancellationToken)
        {
            if (_skipWrites)
            {
                return(Task.CompletedTask);
            }

            var started = _requestContext.Response.HasStarted;

            if (count == 0 && started)
            {
                // No data to send and we've already sent the headers
                return(Task.CompletedTask);
            }

            if (cancellationToken.IsCancellationRequested)
            {
                Abort(ThrowWriteExceptions);
                return(Task.FromCanceled <int>(cancellationToken));
            }

            // We are setting buffer size to 1 to prevent FileStream from allocating it's internal buffer
            // It's too expensive to validate anything before opening the file. Open the file and then check the lengths.
            var fileStream = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, bufferSize: 1,
                                            options: FileOptions.Asynchronous | FileOptions.SequentialScan); // Extremely expensive.

            try
            {
                var length = fileStream.Length; // Expensive, only do it once
                if (!count.HasValue)
                {
                    count = length - offset;
                }
                if (offset < 0 || offset > length)
                {
                    throw new ArgumentOutOfRangeException(nameof(offset), offset, string.Empty);
                }
                if (count < 0 || count > length - offset)
                {
                    throw new ArgumentOutOfRangeException(nameof(count), count, string.Empty);
                }

                CheckWriteCount(count);
            }
            catch
            {
                fileStream.Dispose();
                throw;
            }

            // Make sure all validation is performed before this computes the headers
            var  flags = ComputeLeftToWrite(count.Value);
            uint statusCode;
            uint bytesSent   = 0;
            var  chunked     = _requestContext.Response.BoundaryType == BoundaryType.Chunked;
            var  asyncResult = new ResponseStreamAsyncResult(this, fileStream, offset, count.Value, chunked, cancellationToken);

            try
            {
                if (!started)
                {
                    statusCode = _requestContext.Response.SendHeaders(null, asyncResult, flags, false);
                    bytesSent  = asyncResult.BytesSent;
                }
                else
                {
                    // TODO: If opaque then include the buffer data flag.
                    statusCode = HttpApi.HttpSendResponseEntityBody(
                        RequestQueueHandle,
                        RequestId,
                        (uint)flags,
                        asyncResult.DataChunkCount,
                        asyncResult.DataChunks,
                        &bytesSent,
                        IntPtr.Zero,
                        0,
                        asyncResult.NativeOverlapped,
                        IntPtr.Zero);
                }
            }
            catch (Exception e)
            {
                LogHelper.LogException(Logger, "SendFileAsync", e);
                asyncResult.Dispose();
                Abort();
                throw;
            }

            if (statusCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS && statusCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_IO_PENDING)
            {
                if (cancellationToken.IsCancellationRequested)
                {
                    LogHelper.LogDebug(Logger, "SendFileAsync", $"Write cancelled with error code: {statusCode}");
                    asyncResult.Cancel(ThrowWriteExceptions);
                }
                else if (ThrowWriteExceptions)
                {
                    asyncResult.Dispose();
                    var exception = new IOException(string.Empty, new HttpSysException((int)statusCode));
                    LogHelper.LogException(Logger, "SendFileAsync", exception);
                    Abort();
                    throw exception;
                }
                else
                {
                    // Abort the request but do not close the stream, let future writes complete silently
                    LogHelper.LogDebug(Logger, "SendFileAsync", $"Ignored write exception: {statusCode}");
                    asyncResult.FailSilently();
                }
            }

            if (statusCode == ErrorCodes.ERROR_SUCCESS && HttpSysListener.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 & HttpApiTypes.HTTP_FLAGS.HTTP_SEND_RESPONSE_FLAG_MORE_DATA) == 0)
            {
                _lastWrite = asyncResult;
            }

            return(asyncResult.Task);
        }
Ejemplo n.º 6
0
        // Simpler than Flush because it will never be called at the end of the request from Dispose.
        private unsafe Task FlushInternalAsync(ArraySegment <byte> data, CancellationToken cancellationToken)
        {
            if (_skipWrites)
            {
                return(Task.CompletedTask);
            }

            var started = _requestContext.Response.HasStarted;

            if (data.Count == 0 && started)
            {
                // No data to send and we've already sent the headers
                return(Task.CompletedTask);
            }

            if (cancellationToken.IsCancellationRequested)
            {
                Abort(ThrowWriteExceptions);
                return(Task.FromCanceled <int>(cancellationToken));
            }

            // Make sure all validation is performed before this computes the headers
            var  flags       = ComputeLeftToWrite(data.Count);
            uint statusCode  = 0;
            var  chunked     = _requestContext.Response.BoundaryType == BoundaryType.Chunked;
            var  asyncResult = new ResponseStreamAsyncResult(this, data, chunked, cancellationToken);
            uint bytesSent   = 0;

            try
            {
                if (!started)
                {
                    statusCode = _requestContext.Response.SendHeaders(null, asyncResult, flags, false);
                    bytesSent  = asyncResult.BytesSent;
                }
                else
                {
                    statusCode = HttpApi.HttpSendResponseEntityBody(
                        RequestQueueHandle,
                        RequestId,
                        (uint)flags,
                        asyncResult.DataChunkCount,
                        asyncResult.DataChunks,
                        &bytesSent,
                        IntPtr.Zero,
                        0,
                        asyncResult.NativeOverlapped,
                        IntPtr.Zero);
                }
            }
            catch (Exception e)
            {
                LogHelper.LogException(Logger, "FlushAsync", e);
                asyncResult.Dispose();
                Abort();
                throw;
            }

            if (statusCode != ErrorCodes.ERROR_SUCCESS && statusCode != ErrorCodes.ERROR_IO_PENDING)
            {
                if (cancellationToken.IsCancellationRequested)
                {
                    LogHelper.LogDebug(Logger, "FlushAsync", $"Write cancelled with error code: {statusCode}");
                    asyncResult.Cancel(ThrowWriteExceptions);
                }
                else if (ThrowWriteExceptions)
                {
                    asyncResult.Dispose();
                    Exception exception = new IOException(string.Empty, new HttpSysException((int)statusCode));
                    LogHelper.LogException(Logger, "FlushAsync", exception);
                    Abort();
                    throw exception;
                }
                else
                {
                    // Abort the request but do not close the stream, let future writes complete silently
                    LogHelper.LogDebug(Logger, "FlushAsync", $"Ignored write exception: {statusCode}");
                    asyncResult.FailSilently();
                }
            }

            if (statusCode == ErrorCodes.ERROR_SUCCESS && HttpSysListener.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 & HttpApiTypes.HTTP_FLAGS.HTTP_SEND_RESPONSE_FLAG_MORE_DATA) == 0)
            {
                _lastWrite = asyncResult;
            }

            return(asyncResult.Task);
        }
Ejemplo n.º 7
0
        // We never expect endOfRequest and data at the same time
        private unsafe void FlushInternal(bool endOfRequest, ArraySegment <byte> data = new ArraySegment <byte>())
        {
            Debug.Assert(!(endOfRequest && data.Count > 0), "Data is not supported at the end of the request.");

            if (_skipWrites)
            {
                return;
            }

            var started = _requestContext.Response.HasStarted;

            if (data.Count == 0 && started && !endOfRequest)
            {
                // No data to send and we've already sent the headers
                return;
            }

            // Make sure all validation is performed before this computes the headers
            var flags = ComputeLeftToWrite(data.Count, endOfRequest);

            if (endOfRequest && _leftToWrite > 0)
            {
                _requestContext.Abort();
                // This is logged rather than thrown because it is too late for an exception to be visible in user code.
                LogHelper.LogError(Logger, "ResponseStream::Dispose", "Fewer bytes were written than were specified in the Content-Length.");
                return;
            }

            uint statusCode = 0;

            HttpApiTypes.HTTP_DATA_CHUNK[] dataChunks;
            var pinnedBuffers = PinDataBuffers(endOfRequest, data, out dataChunks);

            try
            {
                if (!started)
                {
                    statusCode = _requestContext.Response.SendHeaders(dataChunks, null, flags, false);
                }
                else
                {
                    fixed(HttpApiTypes.HTTP_DATA_CHUNK *pDataChunks = dataChunks)
                    {
                        statusCode = HttpApi.HttpSendResponseEntityBody(
                            RequestQueueHandle,
                            RequestId,
                            (uint)flags,
                            (ushort)dataChunks.Length,
                            pDataChunks,
                            null,
                            IntPtr.Zero,
                            0,
                            SafeNativeOverlapped.Zero,
                            IntPtr.Zero);
                    }
                }
            }
            finally
            {
                FreeDataBuffers(pinnedBuffers);
            }

            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)))
            {
                if (ThrowWriteExceptions)
                {
                    var exception = new IOException(string.Empty, new HttpSysException((int)statusCode));
                    LogHelper.LogException(Logger, "Flush", exception);
                    Abort();
                    throw exception;
                }
                else
                {
                    // Abort the request but do not close the stream, let future writes complete silently
                    LogHelper.LogDebug(Logger, "Flush", $"Ignored write exception: {statusCode}");
                    Abort(dispose: false);
                }
            }
        }
Ejemplo n.º 8
0
        public Task StartAsync <TContext>(IHttpApplication <TContext> application, CancellationToken cancellationToken)
        {
            if (application == null)
            {
                throw new ArgumentNullException(nameof(application));
            }

            var hostingUrlsPresent = _serverAddresses.Addresses.Count > 0;
            var serverAddressCopy  = _serverAddresses.Addresses.ToList();

            _serverAddresses.Addresses.Clear();

            if (_serverAddresses.PreferHostingUrls && hostingUrlsPresent)
            {
                if (_options.UrlPrefixes.Count > 0)
                {
                    LogHelper.LogWarning(_logger, $"Overriding endpoints added to {nameof(HttpSysOptions.UrlPrefixes)} since {nameof(IServerAddressesFeature.PreferHostingUrls)} is set to true." +
                                         $" Binding to address(es) '{string.Join(", ", _serverAddresses.Addresses)}' instead. ");

                    Listener.Options.UrlPrefixes.Clear();
                }

                UpdateUrlPrefixes(serverAddressCopy);
            }
            else if (_options.UrlPrefixes.Count > 0)
            {
                if (hostingUrlsPresent)
                {
                    LogHelper.LogWarning(_logger, $"Overriding address(es) '{string.Join(", ", _serverAddresses.Addresses)}'. " +
                                         $"Binding to endpoints added to {nameof(HttpSysOptions.UrlPrefixes)} instead.");

                    _serverAddresses.Addresses.Clear();
                }
            }
            else if (hostingUrlsPresent)
            {
                UpdateUrlPrefixes(serverAddressCopy);
            }
            else
            {
                LogHelper.LogDebug(_logger, $"No listening endpoints were configured. Binding to {Constants.DefaultServerAddress} by default.");

                Listener.Options.UrlPrefixes.Add(Constants.DefaultServerAddress);
            }

            // Can't call Start twice
            Contract.Assert(_application == null);

            Contract.Assert(application != null);

            _application = new ApplicationWrapper <TContext>(application);

            Listener.Start();

            // Update server addresses after we start listening as port 0
            // needs to be selected at the point of binding.
            foreach (var prefix in _options.UrlPrefixes)
            {
                _serverAddresses.Addresses.Add(prefix.FullPrefix);
            }

            ActivateRequestProcessingLimits();

            return(Task.CompletedTask);
        }
Ejemplo n.º 9
0
        public Task StartAsync <TContext>(IHttpApplication <TContext> application, CancellationToken cancellationToken)
        {
            if (application == null)
            {
                throw new ArgumentNullException(nameof(application));
            }

            var hostingUrlsPresent = _serverAddresses.Addresses.Count > 0;

            if (_serverAddresses.PreferHostingUrls && hostingUrlsPresent)
            {
                if (_options.UrlPrefixes.Count > 0)
                {
                    LogHelper.LogWarning(_logger, $"Overriding endpoints added to {nameof(HttpSysOptions.UrlPrefixes)} since {nameof(IServerAddressesFeature.PreferHostingUrls)} is set to true." +
                                         $" Binding to address(es) '{string.Join(", ", _serverAddresses.Addresses)}' instead. ");

                    Listener.Options.UrlPrefixes.Clear();
                }

                foreach (var value in _serverAddresses.Addresses)
                {
                    Listener.Options.UrlPrefixes.Add(value);
                }
            }
            else if (_options.UrlPrefixes.Count > 0)
            {
                if (hostingUrlsPresent)
                {
                    LogHelper.LogWarning(_logger, $"Overriding address(es) '{string.Join(", ", _serverAddresses.Addresses)}'. " +
                                         $"Binding to endpoints added to {nameof(HttpSysOptions.UrlPrefixes)} instead.");

                    _serverAddresses.Addresses.Clear();
                }

                foreach (var prefix in _options.UrlPrefixes)
                {
                    _serverAddresses.Addresses.Add(prefix.FullPrefix);
                }
            }
            else if (hostingUrlsPresent)
            {
                foreach (var value in _serverAddresses.Addresses)
                {
                    Listener.Options.UrlPrefixes.Add(value);
                }
            }
            else if (Listener.RequestQueue.Created)
            {
                LogHelper.LogDebug(_logger, $"No listening endpoints were configured. Binding to {Constants.DefaultServerAddress} by default.");

                _serverAddresses.Addresses.Add(Constants.DefaultServerAddress);
                Listener.Options.UrlPrefixes.Add(Constants.DefaultServerAddress);
            }
            // else // Attaching to an existing queue, don't add a default.

            // Can't call Start twice
            Contract.Assert(_application == null);

            Contract.Assert(application != null);

            _application = new ApplicationWrapper <TContext>(application);

            Listener.Start();

            ActivateRequestProcessingLimits();

            return(Task.CompletedTask);
        }