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); } }
internal unsafe void CancelLastWrite(SafeHandle requestQueueHandle) { ResponseStreamAsyncResult asyncState = _lastWrite; if (asyncState != null && !asyncState.IsCompleted) { UnsafeNclNativeMethods.CancelIoEx(requestQueueHandle, asyncState.NativeOverlapped); } }
/* * 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(HttpApi.HTTP_DATA_CHUNK[] dataChunks, ResponseStreamAsyncResult asyncResult, HttpApi.HTTP_FLAGS flags, bool isOpaqueUpgrade) { Debug.Assert(!HasStartedSending, "HttpListenerResponse::SendHeaders()|SentHeaders is true."); _responseState = ResponseState.StartedSending; var reasonPhrase = GetReasonPhrase(StatusCode); /* * if (m_BoundaryType==BoundaryType.Raw) { * use HTTP_SEND_RESPONSE_FLAG_RAW_HEADER; * } */ 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 = (HttpApi.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 HttpApi.HTTP_CACHE_POLICY(); var cacheTtl = CacheTtl; if (cacheTtl.HasValue && cacheTtl.Value > TimeSpan.Zero) { cachePolicy.Policy = HttpApi.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 = (sbyte *)pReasonPhrase; fixed(HttpApi.HTTP_RESPONSE_V2 *pResponse = &_nativeResponse) { statusCode = HttpApi.HttpSendHttpResponse( RequestContext.RequestQueueHandle, Request.RequestId, (uint)flags, pResponse, &cachePolicy, &bytesSent, SafeLocalFree.Zero, 0, asyncResult == null ? SafeNativeOverlapped.Zero : asyncResult.NativeOverlapped, IntPtr.Zero); if (asyncResult != null && statusCode == ErrorCodes.ERROR_SUCCESS && WebListener.SkipIOCPCallbackOnSuccess) { asyncResult.BytesSent = bytesSent; // The caller will invoke IOCompleted } } } } finally { FreePinnedHeaders(pinnedHeaders); } return(statusCode); }
internal unsafe Task SendFileAsyncCore(string fileName, long offset, long?count, CancellationToken cancellationToken) { _requestContext.Response.Start(); UnsafeNclNativeMethods.HttpApi.HTTP_FLAGS flags = ComputeLeftToWrite(); if (count == 0 && _leftToWrite != 0) { return(Helpers.CompletedTask()); } if (_leftToWrite >= 0 && count > _leftToWrite) { throw new InvalidOperationException(Resources.Exception_TooMuchWritten); } // TODO: Verbose log if (cancellationToken.IsCancellationRequested) { return(Helpers.CanceledTask <int>()); } var cancellationRegistration = default(CancellationTokenRegistration); if (cancellationToken.CanBeCanceled) { cancellationRegistration = cancellationToken.Register(RequestContext.AbortDelegate, _requestContext); } uint statusCode; uint bytesSent = 0; bool startedSending = _requestContext.Response.HasStartedSending; var chunked = _requestContext.Response.BoundaryType == BoundaryType.Chunked; ResponseStreamAsyncResult asyncResult = new ResponseStreamAsyncResult(this, fileName, offset, count, chunked, cancellationRegistration); long bytesWritten; if (chunked) { bytesWritten = 0; } else if (count.HasValue) { bytesWritten = count.Value; } else { bytesWritten = asyncResult.FileLength - offset; } // Update _leftToWrite now so we can queue up additional calls to SendFileAsync. flags |= _leftToWrite == bytesWritten ? HttpApi.HTTP_FLAGS.NONE : HttpApi.HTTP_FLAGS.HTTP_SEND_RESPONSE_FLAG_MORE_DATA; UpdateWritenCount((uint)bytesWritten); try { if (!startedSending) { statusCode = _requestContext.Response.SendHeaders(null, asyncResult, flags, false); bytesSent = asyncResult.BytesSent; } else { // TODO: If opaque then include the buffer data flag. statusCode = HttpApi.HttpSendResponseEntityBody( _requestContext.RequestQueueHandle, _requestContext.RequestId, (uint)flags, asyncResult.DataChunkCount, asyncResult.DataChunks, &bytesSent, SafeLocalFree.Zero, 0, asyncResult.NativeOverlapped, IntPtr.Zero); } } catch (Exception e) { LogHelper.LogException(_requestContext.Logger, "SendFileAsync", e); asyncResult.Dispose(); Abort(); throw; } if (statusCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_SUCCESS && statusCode != UnsafeNclNativeMethods.ErrorCodes.ERROR_IO_PENDING) { asyncResult.Dispose(); if (_requestContext.Server.IgnoreWriteExceptions && startedSending) { asyncResult.Complete(); } else { Exception exception = new IOException(string.Empty, new WebListenerException((int)statusCode)); LogHelper.LogException(_requestContext.Logger, "SendFileAsync", exception); Abort(); throw exception; } } if (statusCode == ErrorCodes.ERROR_SUCCESS && WebListener.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 & HttpApi.HTTP_FLAGS.HTTP_SEND_RESPONSE_FLAG_MORE_DATA) == 0) { _lastWrite = asyncResult; } return(asyncResult.Task); }
// Simpler than Flush because it will never be called at the end of the request from Dispose. public unsafe override Task FlushAsync(CancellationToken cancellationToken) { if (_closed) { return(Helpers.CompletedTask()); } bool startedSending = _requestContext.Response.HasStartedSending; var byteCount = _buffer.TotalBytes; if (byteCount == 0 && startedSending) { // Empty flush return(Helpers.CompletedTask()); } var cancellationRegistration = default(CancellationTokenRegistration); if (cancellationToken.CanBeCanceled) { cancellationRegistration = cancellationToken.Register(RequestContext.AbortDelegate, _requestContext); } var flags = ComputeLeftToWrite(); if (_leftToWrite != byteCount) { flags |= HttpApi.HTTP_FLAGS.HTTP_SEND_RESPONSE_FLAG_MORE_DATA; } UpdateWritenCount((uint)byteCount); uint statusCode = 0; var chunked = _requestContext.Response.BoundaryType == BoundaryType.Chunked; var asyncResult = new ResponseStreamAsyncResult(this, _buffer, chunked, cancellationRegistration); uint bytesSent = 0; try { if (!startedSending) { statusCode = _requestContext.Response.SendHeaders(null, asyncResult, flags, false); bytesSent = asyncResult.BytesSent; } else { statusCode = HttpApi.HttpSendResponseEntityBody( _requestContext.RequestQueueHandle, _requestContext.RequestId, (uint)flags, asyncResult.DataChunkCount, asyncResult.DataChunks, &bytesSent, SafeLocalFree.Zero, 0, asyncResult.NativeOverlapped, IntPtr.Zero); } } catch (Exception e) { LogHelper.LogException(_requestContext.Logger, "FlushAsync", e); asyncResult.Dispose(); Abort(); throw; } if (statusCode != ErrorCodes.ERROR_SUCCESS && statusCode != ErrorCodes.ERROR_IO_PENDING) { asyncResult.Dispose(); if (_requestContext.Server.IgnoreWriteExceptions && startedSending) { asyncResult.Complete(); } else { Exception exception = new IOException(string.Empty, new WebListenerException((int)statusCode)); LogHelper.LogException(_requestContext.Logger, "FlushAsync", exception); Abort(); throw exception; } } if (statusCode == ErrorCodes.ERROR_SUCCESS && WebListener.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 & HttpApi.HTTP_FLAGS.HTTP_SEND_RESPONSE_FLAG_MORE_DATA) == 0) { _lastWrite = asyncResult; } return(asyncResult.Task); }