Example #1
0
    // The final Content-Length async write can only be Canceled by CancelIoEx.
    // Sync can only be Canceled by CancelSynchronousIo, but we don't attempt this right now.
    internal unsafe void CancelLastWrite()
    {
        ResponseStreamAsyncResult?asyncState = _lastWrite;

        if (asyncState != null && !asyncState.IsCompleted)
        {
            UnsafeNclNativeMethods.CancelIoEx(RequestQueueHandle, asyncState.NativeOverlapped !);
        }
    }
Example #2
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;
        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)
        {
            Log.ErrorWhenFlushAsync(Logger, e);
            asyncResult.Dispose();
            Abort();
            throw;
        }

        if (statusCode != ErrorCodes.ERROR_SUCCESS && statusCode != ErrorCodes.ERROR_IO_PENDING)
        {
            if (cancellationToken.IsCancellationRequested)
            {
                Log.WriteFlushCancelled(Logger, statusCode);
                asyncResult.Cancel(ThrowWriteExceptions);
            }
            else if (ThrowWriteExceptions)
            {
                asyncResult.Dispose();
                Exception exception = new IOException(string.Empty, new HttpSysException((int)statusCode));
                Log.ErrorWhenFlushAsync(Logger, exception);
                Abort();
                throw exception;
            }
            else
            {
                // Abort the request but do not close the stream, let future writes complete silently
                Log.WriteErrorIgnored(Logger, 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);
    }
Example #3
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)
        {
            Log.FileSendAsyncError(Logger, e);
            asyncResult.Dispose();
            Abort();
            throw;
        }

        if (statusCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS && statusCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_IO_PENDING)
        {
            if (cancellationToken.IsCancellationRequested)
            {
                Log.FileSendAsyncCancelled(Logger, statusCode);
                asyncResult.Cancel(ThrowWriteExceptions);
            }
            else if (ThrowWriteExceptions)
            {
                asyncResult.Dispose();
                var exception = new IOException(string.Empty, new HttpSysException((int)statusCode));
                Log.FileSendAsyncError(Logger, exception);
                Abort();
                throw exception;
            }
            else
            {
                // Abort the request but do not close the stream, let future writes complete
                Log.FileSendAsyncErrorIgnored(Logger, 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);
    }
Example #4
0
    /*
     * 12.3
     * HttpSendHttpResponse() and HttpSendResponseEntityBody() Flag Values.
     * The following flags can be used on calls to HttpSendHttpResponse() and HttpSendResponseEntityBody() API calls:
     *
     #define HTTP_SEND_RESPONSE_FLAG_DISCONNECT          0x00000001
     #define HTTP_SEND_RESPONSE_FLAG_MORE_DATA           0x00000002
     #define HTTP_SEND_RESPONSE_FLAG_RAW_HEADER          0x00000004
     #define HTTP_SEND_RESPONSE_FLAG_VALID               0x00000007
     *
     * HTTP_SEND_RESPONSE_FLAG_DISCONNECT:
     *  specifies that the network connection should be disconnected immediately after
     *  sending the response, overriding the HTTP protocol's persistent connection features.
     * HTTP_SEND_RESPONSE_FLAG_MORE_DATA:
     *  specifies that additional entity body data will be sent by the caller. Thus,
     *  the last call HttpSendResponseEntityBody for a RequestId, will have this flag reset.
     * HTTP_SEND_RESPONSE_RAW_HEADER:
     *  specifies that a caller of HttpSendResponseEntityBody() is intentionally omitting
     *  a call to HttpSendHttpResponse() in order to bypass normal header processing. The
     *  actual HTTP header will be generated by the application and sent as entity body.
     *  This flag should be passed on the first call to HttpSendResponseEntityBody, and
     *  not after. Thus, flag is not applicable to HttpSendHttpResponse.
     */

    // TODO: Consider using HTTP_SEND_RESPONSE_RAW_HEADER with HttpSendResponseEntityBody instead of calling HttpSendHttpResponse.
    // This will give us more control of the bytes that hit the wire, including encodings, HTTP 1.0, etc..
    // It may also be faster to do this work in managed code and then pass down only one buffer.
    // 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(HttpApiTypes.HTTP_DATA_CHUNK[]?dataChunks,
                                     ResponseStreamAsyncResult?asyncResult,
                                     HttpApiTypes.HTTP_FLAGS flags,
                                     bool isOpaqueUpgrade)
    {
        Debug.Assert(!HasStarted, "HttpListenerResponse::SendHeaders()|SentHeaders is true.");

        _responseState = ResponseState.Started;
        var reasonPhrase = GetReasonPhrase(StatusCode);

        uint            statusCode;
        uint            bytesSent;
        List <GCHandle>?pinnedHeaders = SerializeHeaders(isOpaqueUpgrade);

        try
        {
            if (dataChunks != null)
            {
                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    = (HttpApiTypes.HTTP_DATA_CHUNK *)handle.AddrOfPinnedObject();
            }
            else if (asyncResult != null && asyncResult.DataChunks != null)
            {
                _nativeResponse.Response_V1.EntityChunkCount = asyncResult.DataChunkCount;
                _nativeResponse.Response_V1.pEntityChunks    = asyncResult.DataChunks;
            }
            else
            {
                _nativeResponse.Response_V1.EntityChunkCount = 0;
                _nativeResponse.Response_V1.pEntityChunks    = null;
            }

            var cachePolicy = new HttpApiTypes.HTTP_CACHE_POLICY();
            if (_cacheTtl.HasValue && _cacheTtl.Value > TimeSpan.Zero)
            {
                cachePolicy.Policy        = HttpApiTypes.HTTP_CACHE_POLICY_TYPE.HttpCachePolicyTimeToLive;
                cachePolicy.SecondsToLive = (uint)Math.Min(_cacheTtl.Value.Ticks / TimeSpan.TicksPerSecond, Int32.MaxValue);
            }

            byte[] reasonPhraseBytes = HeaderEncoding.GetBytes(reasonPhrase);
            fixed(byte *pReasonPhrase = reasonPhraseBytes)
            {
                _nativeResponse.Response_V1.ReasonLength = (ushort)reasonPhraseBytes.Length;
                _nativeResponse.Response_V1.pReason      = (byte *)pReasonPhrase;
                fixed(HttpApiTypes.HTTP_RESPONSE_V2 *pResponse = &_nativeResponse)
                {
                    statusCode =
                        HttpApi.HttpSendHttpResponse(
                            RequestContext.Server.RequestQueue.Handle,
                            Request.RequestId,
                            (uint)flags,
                            pResponse,
                            &cachePolicy,
                            &bytesSent,
                            IntPtr.Zero,
                            0,
                            asyncResult == null ? SafeNativeOverlapped.Zero : asyncResult.NativeOverlapped !,
                            IntPtr.Zero);

                    // GoAway is only supported on later versions. Retry.
                    if (statusCode == ErrorCodes.ERROR_INVALID_PARAMETER &&
                        (flags & HttpApiTypes.HTTP_FLAGS.HTTP_SEND_RESPONSE_FLAG_GOAWAY) != 0)
                    {
                        flags     &= ~HttpApiTypes.HTTP_FLAGS.HTTP_SEND_RESPONSE_FLAG_GOAWAY;
                        statusCode =
                            HttpApi.HttpSendHttpResponse(
                                RequestContext.Server.RequestQueue.Handle,
                                Request.RequestId,
                                (uint)flags,
                                pResponse,
                                &cachePolicy,
                                &bytesSent,
                                IntPtr.Zero,
                                0,
                                asyncResult == null ? SafeNativeOverlapped.Zero : asyncResult.NativeOverlapped !,
                                IntPtr.Zero);

                        // Succeeded without GoAway, disable them.
                        if (statusCode != ErrorCodes.ERROR_INVALID_PARAMETER)
                        {
                            SupportsGoAway = false;
                        }
                    }

                    if (asyncResult != null &&
                        statusCode == ErrorCodes.ERROR_SUCCESS &&
                        HttpSysListener.SkipIOCPCallbackOnSuccess)
                    {
                        asyncResult.BytesSent = bytesSent;
                        // The caller will invoke IOCompleted
                    }
                }
            }
        }
        finally
        {
            FreePinnedHeaders(pinnedHeaders);
        }
        return(statusCode);
    }