private void WriteCore(byte[] buffer, int offset, int size) { Interop.HttpApi.HTTP_FLAGS flags = ComputeLeftToWrite(); if (size == 0 && _leftToWrite != 0) { return; } if (_leftToWrite >= 0 && size > _leftToWrite) { throw new ProtocolViolationException(SR.net_entitytoobig); } uint statusCode; uint dataToWrite = (uint)size; SafeLocalAllocHandle?bufferAsIntPtr = null; IntPtr pBufferAsIntPtr = IntPtr.Zero; bool sentHeaders = _httpContext.Response.SentHeaders; try { if (size == 0) { statusCode = _httpContext.Response.SendHeaders(null, null, flags, false); } else { fixed(byte *pDataBuffer = buffer) { byte *pBuffer = pDataBuffer; if (_httpContext.Response.BoundaryType == BoundaryType.Chunked) { string chunkHeader = size.ToString("x", CultureInfo.InvariantCulture); dataToWrite = dataToWrite + (uint)(chunkHeader.Length + 4); bufferAsIntPtr = SafeLocalAllocHandle.LocalAlloc((int)dataToWrite); pBufferAsIntPtr = bufferAsIntPtr.DangerousGetHandle(); for (int i = 0; i < chunkHeader.Length; i++) { Marshal.WriteByte(pBufferAsIntPtr, i, (byte)chunkHeader[i]); } Marshal.WriteInt16(pBufferAsIntPtr, chunkHeader.Length, 0x0A0D); Marshal.Copy(buffer, offset, pBufferAsIntPtr + chunkHeader.Length + 2, size); Marshal.WriteInt16(pBufferAsIntPtr, (int)(dataToWrite - 2), 0x0A0D); pBuffer = (byte *)pBufferAsIntPtr; offset = 0; } Interop.HttpApi.HTTP_DATA_CHUNK dataChunk = default; dataChunk.DataChunkType = Interop.HttpApi.HTTP_DATA_CHUNK_TYPE.HttpDataChunkFromMemory; dataChunk.pBuffer = (byte *)(pBuffer + offset); dataChunk.BufferLength = dataToWrite; flags |= _leftToWrite == size ? Interop.HttpApi.HTTP_FLAGS.NONE : Interop.HttpApi.HTTP_FLAGS.HTTP_SEND_RESPONSE_FLAG_MORE_DATA; if (!sentHeaders) { statusCode = _httpContext.Response.SendHeaders(&dataChunk, null, flags, false); } else { if (NetEventSource.Log.IsEnabled()) { NetEventSource.Info(this, "Calling Interop.HttpApi.HttpSendResponseEntityBody"); } statusCode = Interop.HttpApi.HttpSendResponseEntityBody( _httpContext.RequestQueueHandle, _httpContext.RequestId, (uint)flags, 1, &dataChunk, null, SafeLocalAllocHandle.Zero, 0, null, null); if (NetEventSource.Log.IsEnabled()) { NetEventSource.Info(this, "Call to Interop.HttpApi.HttpSendResponseEntityBody returned:" + statusCode); } if (_httpContext.Listener !.IgnoreWriteExceptions) { if (NetEventSource.Log.IsEnabled()) { NetEventSource.Info(this, "Write() suppressing error"); } statusCode = Interop.HttpApi.ERROR_SUCCESS; } } } } } finally { // free unmanaged buffer bufferAsIntPtr?.Close(); } if (statusCode != Interop.HttpApi.ERROR_SUCCESS && statusCode != Interop.HttpApi.ERROR_HANDLE_EOF) { Exception exception = new HttpListenerException((int)statusCode); if (NetEventSource.Log.IsEnabled()) { NetEventSource.Error(this, exception.ToString()); } _closed = true; _httpContext.Abort(); throw exception; } UpdateAfterWrite(dataToWrite); if (NetEventSource.Log.IsEnabled()) { NetEventSource.DumpBuffer(this, buffer, offset, (int)dataToWrite); } }
// return value indicates sync vs async completion // false: sync completion // true: async completion or with error private unsafe bool WriteAsyncFast(HttpListenerAsyncEventArgs eventArgs) { Interop.HttpApi.HTTP_FLAGS flags = Interop.HttpApi.HTTP_FLAGS.NONE; eventArgs.StartOperationCommon(this, _outputStream.InternalHttpContext.RequestQueueBoundHandle); eventArgs.StartOperationSend(); uint statusCode; bool completedAsynchronouslyOrWithError; try { if (_outputStream.Closed || (eventArgs.Buffer != null && eventArgs.Count == 0)) { eventArgs.FinishOperationSuccess(eventArgs.Count, true); return(false); } if (eventArgs.ShouldCloseOutput) { flags |= Interop.HttpApi.HTTP_FLAGS.HTTP_SEND_RESPONSE_FLAG_DISCONNECT; } else { flags |= Interop.HttpApi.HTTP_FLAGS.HTTP_SEND_RESPONSE_FLAG_MORE_DATA; // When using HTTP_SEND_RESPONSE_FLAG_BUFFER_DATA HTTP.SYS will copy the payload to // kernel memory (Non-Paged Pool). Http.Sys will buffer up to // Math.Min(16 MB, current TCP window size) flags |= Interop.HttpApi.HTTP_FLAGS.HTTP_SEND_RESPONSE_FLAG_BUFFER_DATA; } uint bytesSent; statusCode = Interop.HttpApi.HttpSendResponseEntityBody( _outputStream.InternalHttpContext.RequestQueueHandle, _outputStream.InternalHttpContext.RequestId, (uint)flags, eventArgs.EntityChunkCount, (Interop.HttpApi.HTTP_DATA_CHUNK *)eventArgs.EntityChunks, &bytesSent, SafeLocalAllocHandle.Zero, 0, eventArgs.NativeOverlapped, null); if (statusCode != Interop.HttpApi.ERROR_SUCCESS && statusCode != Interop.HttpApi.ERROR_IO_PENDING) { throw new HttpListenerException((int)statusCode); } else if (statusCode == Interop.HttpApi.ERROR_SUCCESS && HttpListener.SkipIOCPCallbackOnSuccess) { // IO operation completed synchronously - callback won't be called to signal completion. eventArgs.FinishOperationSuccess((int)bytesSent, true); completedAsynchronouslyOrWithError = false; } else { completedAsynchronouslyOrWithError = true; } } catch (Exception e) { _writeEventArgs !.FinishOperationFailure(e, true); _outputStream.SetClosedFlag(); _outputStream.InternalHttpContext.Abort(); completedAsynchronouslyOrWithError = true; } return(completedAsynchronouslyOrWithError); }
private IAsyncResult BeginWriteCore(byte[] buffer, int offset, int size, AsyncCallback?callback, object?state) { Interop.HttpApi.HTTP_FLAGS flags = ComputeLeftToWrite(); if (_closed || (size == 0 && _leftToWrite != 0)) { HttpResponseStreamAsyncResult result = new HttpResponseStreamAsyncResult(this, state, callback); result.InvokeCallback((uint)0); return(result); } if (_leftToWrite >= 0 && size > _leftToWrite) { throw new ProtocolViolationException(SR.net_entitytoobig); } uint statusCode; uint bytesSent = 0; flags |= _leftToWrite == size ? Interop.HttpApi.HTTP_FLAGS.NONE : Interop.HttpApi.HTTP_FLAGS.HTTP_SEND_RESPONSE_FLAG_MORE_DATA; bool sentHeaders = _httpContext.Response.SentHeaders; HttpResponseStreamAsyncResult asyncResult = new HttpResponseStreamAsyncResult(this, state, callback, buffer, offset, size, _httpContext.Response.BoundaryType == BoundaryType.Chunked, sentHeaders, _httpContext.RequestQueueBoundHandle); // Update m_LeftToWrite now so we can queue up additional BeginWrite's without waiting for EndWrite. UpdateAfterWrite((uint)((_httpContext.Response.BoundaryType == BoundaryType.Chunked) ? 0 : size)); try { if (!sentHeaders) { statusCode = _httpContext.Response.SendHeaders(null, asyncResult, flags, false); } else { if (NetEventSource.Log.IsEnabled()) { NetEventSource.Info(this, "Calling Interop.HttpApi.HttpSendResponseEntityBody"); } statusCode = Interop.HttpApi.HttpSendResponseEntityBody( _httpContext.RequestQueueHandle, _httpContext.RequestId, (uint)flags, asyncResult.dataChunkCount, asyncResult.pDataChunks, &bytesSent, SafeLocalAllocHandle.Zero, 0, asyncResult._pOverlapped, null); if (NetEventSource.Log.IsEnabled()) { NetEventSource.Info(this, "Call to Interop.HttpApi.HttpSendResponseEntityBody returned:" + statusCode); } } } catch (Exception e) { if (NetEventSource.Log.IsEnabled()) { NetEventSource.Error(this, e.ToString()); } asyncResult.InternalCleanup(); _closed = true; _httpContext.Abort(); throw; } if (statusCode != Interop.HttpApi.ERROR_SUCCESS && statusCode != Interop.HttpApi.ERROR_IO_PENDING) { asyncResult.InternalCleanup(); if (_httpContext.Listener !.IgnoreWriteExceptions && sentHeaders) { if (NetEventSource.Log.IsEnabled()) { NetEventSource.Info(this, "BeginWrite() Suppressing error"); } } else { Exception exception = new HttpListenerException((int)statusCode); if (NetEventSource.Log.IsEnabled()) { NetEventSource.Error(this, exception.ToString()); } _closed = true; _httpContext.Abort(); throw exception; } }
private void DisposeCore() { Interop.HttpApi.HTTP_FLAGS flags = ComputeLeftToWrite(); if (_leftToWrite > 0 && !_inOpaqueMode) { throw new InvalidOperationException(SR.net_io_notenoughbyteswritten); } bool sentHeaders = _httpContext.Response.SentHeaders; if (sentHeaders && _leftToWrite == 0) { return; } uint statusCode = 0; if ((_httpContext.Response.BoundaryType == BoundaryType.Chunked || _httpContext.Response.BoundaryType == BoundaryType.None) && !string.Equals(_httpContext.Request.HttpMethod, "HEAD", StringComparison.OrdinalIgnoreCase)) { if (_httpContext.Response.BoundaryType == BoundaryType.None) { flags |= Interop.HttpApi.HTTP_FLAGS.HTTP_SEND_RESPONSE_FLAG_DISCONNECT; } fixed(void *pBuffer = &s_chunkTerminator[0]) { Interop.HttpApi.HTTP_DATA_CHUNK *pDataChunk = null; if (_httpContext.Response.BoundaryType == BoundaryType.Chunked) { Interop.HttpApi.HTTP_DATA_CHUNK dataChunk = default; dataChunk.DataChunkType = Interop.HttpApi.HTTP_DATA_CHUNK_TYPE.HttpDataChunkFromMemory; dataChunk.pBuffer = (byte *)pBuffer; dataChunk.BufferLength = (uint)s_chunkTerminator.Length; pDataChunk = &dataChunk; } if (!sentHeaders) { statusCode = _httpContext.Response.SendHeaders(pDataChunk, null, flags, false); } else { if (NetEventSource.Log.IsEnabled()) { NetEventSource.Info(this, "Calling Interop.HttpApi.HttpSendResponseEntityBody"); } statusCode = Interop.HttpApi.HttpSendResponseEntityBody( _httpContext.RequestQueueHandle, _httpContext.RequestId, (uint)flags, pDataChunk != null ? (ushort)1 : (ushort)0, pDataChunk, null, SafeLocalAllocHandle.Zero, 0, null, null); if (NetEventSource.Log.IsEnabled()) { NetEventSource.Info(this, "Call to Interop.HttpApi.HttpSendResponseEntityBody returned:" + statusCode); } if (_httpContext.Listener.IgnoreWriteExceptions) { if (NetEventSource.Log.IsEnabled()) { NetEventSource.Info(this, "Suppressing error"); } statusCode = Interop.HttpApi.ERROR_SUCCESS; } } } } else { if (!sentHeaders) { statusCode = _httpContext.Response.SendHeaders(null, null, flags, false); } } if (statusCode != Interop.HttpApi.ERROR_SUCCESS && statusCode != Interop.HttpApi.ERROR_HANDLE_EOF) { Exception exception = new HttpListenerException((int)statusCode); if (NetEventSource.Log.IsEnabled()) { NetEventSource.Error(this, exception.ToString()); } _httpContext.Abort(); throw exception; } _leftToWrite = 0; }
public override IAsyncResult BeginWrite(byte[] buffer, int offset, int size, AsyncCallback callback, object state) { if (NetEventSource.IsEnabled) { NetEventSource.Info(this, "buffer.Length:" + buffer.Length + " size:" + size + " offset:" + offset); } if (buffer == null) { throw new ArgumentNullException(nameof(buffer)); } if (offset < 0 || offset > buffer.Length) { throw new ArgumentOutOfRangeException(nameof(offset)); } if (size < 0 || size > buffer.Length - offset) { throw new ArgumentOutOfRangeException(nameof(size)); } Interop.HttpApi.HTTP_FLAGS flags = ComputeLeftToWrite(); if (_closed || (size == 0 && _leftToWrite != 0)) { if (NetEventSource.IsEnabled) { NetEventSource.Exit(this); } HttpResponseStreamAsyncResult result = new HttpResponseStreamAsyncResult(this, state, callback); result.InvokeCallback((uint)0); return(result); } if (_leftToWrite >= 0 && size > _leftToWrite) { throw new ProtocolViolationException(SR.net_entitytoobig); } uint statusCode; uint bytesSent = 0; flags |= _leftToWrite == size ? Interop.HttpApi.HTTP_FLAGS.NONE : Interop.HttpApi.HTTP_FLAGS.HTTP_SEND_RESPONSE_FLAG_MORE_DATA; bool sentHeaders = _httpContext.Response.SentHeaders; HttpResponseStreamAsyncResult asyncResult = new HttpResponseStreamAsyncResult(this, state, callback, buffer, offset, size, _httpContext.Response.BoundaryType == BoundaryType.Chunked, sentHeaders, _httpContext.RequestQueueBoundHandle); // Update m_LeftToWrite now so we can queue up additional BeginWrite's without waiting for EndWrite. UpdateAfterWrite((uint)((_httpContext.Response.BoundaryType == BoundaryType.Chunked) ? 0 : size)); try { if (!sentHeaders) { statusCode = _httpContext.Response.SendHeaders(null, asyncResult, flags, false); } else { if (NetEventSource.IsEnabled) { NetEventSource.Info(this, "Calling Interop.HttpApi.HttpSendResponseEntityBody"); } statusCode = Interop.HttpApi.HttpSendResponseEntityBody( _httpContext.RequestQueueHandle, _httpContext.RequestId, (uint)flags, asyncResult.dataChunkCount, asyncResult.pDataChunks, &bytesSent, SafeLocalAllocHandle.Zero, 0, asyncResult._pOverlapped, null); if (NetEventSource.IsEnabled) { NetEventSource.Info(this, "Call to Interop.HttpApi.HttpSendResponseEntityBody returned:" + statusCode); } } } catch (Exception e) { if (NetEventSource.IsEnabled) { NetEventSource.Error(this, e.ToString()); } asyncResult.InternalCleanup(); _closed = true; _httpContext.Abort(); throw; } if (statusCode != Interop.HttpApi.ERROR_SUCCESS && statusCode != Interop.HttpApi.ERROR_IO_PENDING) { asyncResult.InternalCleanup(); if (_httpContext.Listener.IgnoreWriteExceptions && sentHeaders) { if (NetEventSource.IsEnabled) { NetEventSource.Info(this, "BeginWrite() Suppressing error"); } } else { Exception exception = new HttpListenerException((int)statusCode); if (NetEventSource.IsEnabled) { NetEventSource.Error(this, exception.ToString()); } _closed = true; _httpContext.Abort(); throw exception; } } if (statusCode == Interop.HttpApi.ERROR_SUCCESS && HttpListener.SkipIOCPCallbackOnSuccess) { // IO operation completed synchronously - callback won't be called to signal completion. asyncResult.IOCompleted(statusCode, bytesSent); } // Last write, cache it for special cancelation handling. if ((flags & Interop.HttpApi.HTTP_FLAGS.HTTP_SEND_RESPONSE_FLAG_MORE_DATA) == 0) { _lastWrite = asyncResult; } if (NetEventSource.IsEnabled) { NetEventSource.Exit(this); } return(asyncResult); }
internal Interop.HttpApi.HTTP_FLAGS ComputeHeaders() { Interop.HttpApi.HTTP_FLAGS flags = Interop.HttpApi.HTTP_FLAGS.NONE; if (NetEventSource.IsEnabled) { NetEventSource.Info(this); } Debug.Assert(!ComputedHeaders, "ComputedHeaders is true."); _responseState = ResponseState.ComputedHeaders; ComputeCoreHeaders(); if (NetEventSource.IsEnabled) { NetEventSource.Info(this, $"flags: {flags} _boundaryType: {_boundaryType} _contentLength: {_contentLength} _keepAlive: {_keepAlive}"); } if (_boundaryType == BoundaryType.None) { if (HttpListenerRequest.ProtocolVersion.Minor == 0) { _keepAlive = false; } else { _boundaryType = BoundaryType.Chunked; } if (CanSendResponseBody(_httpContext.Response.StatusCode)) { _contentLength = -1; } else { ContentLength64 = 0; } } if (NetEventSource.IsEnabled) { NetEventSource.Info(this, $"flags:{flags} _BoundaryType:{_boundaryType} _contentLength:{_contentLength} _keepAlive: {_keepAlive}"); } if (_boundaryType == BoundaryType.ContentLength) { Headers[HttpResponseHeader.ContentLength] = _contentLength.ToString("D", NumberFormatInfo.InvariantInfo); if (_contentLength == 0) { flags = Interop.HttpApi.HTTP_FLAGS.NONE; } } else if (_boundaryType == BoundaryType.Chunked) { Headers[HttpResponseHeader.TransferEncoding] = "chunked"; } else if (_boundaryType == BoundaryType.None) { flags = Interop.HttpApi.HTTP_FLAGS.NONE; // seems like HTTP_SEND_RESPONSE_FLAG_MORE_DATA but this hangs the app; } else { _keepAlive = false; } if (!_keepAlive) { Headers.Add(HttpResponseHeader.Connection, "close"); if (flags == Interop.HttpApi.HTTP_FLAGS.NONE) { flags = Interop.HttpApi.HTTP_FLAGS.HTTP_SEND_RESPONSE_FLAG_DISCONNECT; } } else { if (HttpListenerRequest.ProtocolVersion.Minor == 0) { Headers[HttpResponseHeader.KeepAlive] = "true"; } } if (NetEventSource.IsEnabled) { NetEventSource.Info(this, $"flags:{flags} _BoundaryType:{_boundaryType} _contentLength:{_contentLength} _keepAlive: {_keepAlive}"); } return(flags); }
/* * 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. */ internal unsafe uint SendHeaders(Interop.HttpApi.HTTP_DATA_CHUNK *pDataChunk, HttpResponseStreamAsyncResult asyncResult, Interop.HttpApi.HTTP_FLAGS flags, bool isWebSocketHandshake) { if (NetEventSource.IsEnabled) { NetEventSource.Info(this, $"pDataChunk: { ((IntPtr)pDataChunk)}, asyncResult: {asyncResult}"); } Debug.Assert(!SentHeaders, "SentHeaders is true."); if (StatusCode == (int)HttpStatusCode.Unauthorized) { // User set 401 // Using the configured Auth schemes, populate the auth challenge headers. This is for scenarios where // Anonymous access is allowed for some resources, but the server later determines that authorization // is required for this request. HttpListenerContext.SetAuthenticationHeaders(); } // Log headers if (NetEventSource.IsEnabled) { StringBuilder sb = new StringBuilder("HttpListenerResponse Headers:\n"); for (int i = 0; i < Headers.Count; i++) { sb.Append("\t"); sb.Append(Headers.GetKey(i)); sb.Append(" : "); sb.Append(Headers.Get(i)); sb.Append("\n"); } if (NetEventSource.IsEnabled) { NetEventSource.Info(this, sb.ToString()); } } _responseState = ResponseState.SentHeaders; uint statusCode; uint bytesSent; List <GCHandle> pinnedHeaders = SerializeHeaders(ref _nativeResponse.Headers, isWebSocketHandshake); try { if (pDataChunk != null) { _nativeResponse.EntityChunkCount = 1; _nativeResponse.pEntityChunks = pDataChunk; } else if (asyncResult != null && asyncResult.pDataChunks != null) { _nativeResponse.EntityChunkCount = asyncResult.dataChunkCount; _nativeResponse.pEntityChunks = asyncResult.pDataChunks; } else { _nativeResponse.EntityChunkCount = 0; _nativeResponse.pEntityChunks = null; } if (NetEventSource.IsEnabled) { NetEventSource.Info(this, "Calling Interop.HttpApi.HttpSendHttpResponse flags:" + flags); } if (StatusDescription.Length > 0) { byte[] statusDescriptionBytes = new byte[WebHeaderEncoding.GetByteCount(StatusDescription)]; fixed(byte *pStatusDescription = statusDescriptionBytes) { _nativeResponse.ReasonLength = (ushort)statusDescriptionBytes.Length; WebHeaderEncoding.GetBytes(StatusDescription, 0, statusDescriptionBytes.Length, statusDescriptionBytes, 0); _nativeResponse.pReason = (sbyte *)pStatusDescription; fixed(Interop.HttpApi.HTTP_RESPONSE *pResponse = &_nativeResponse) { statusCode = Interop.HttpApi.HttpSendHttpResponse( HttpListenerContext.RequestQueueHandle, HttpListenerRequest.RequestId, (uint)flags, pResponse, null, &bytesSent, SafeLocalAllocHandle.Zero, 0, asyncResult == null ? null : asyncResult._pOverlapped, null); if (asyncResult != null && statusCode == Interop.HttpApi.ERROR_SUCCESS && HttpListener.SkipIOCPCallbackOnSuccess) { asyncResult.IOCompleted(statusCode, bytesSent); // IO operation completed synchronously - callback won't be called to signal completion. } } } } else { fixed(Interop.HttpApi.HTTP_RESPONSE *pResponse = &_nativeResponse) { statusCode = Interop.HttpApi.HttpSendHttpResponse( HttpListenerContext.RequestQueueHandle, HttpListenerRequest.RequestId, (uint)flags, pResponse, null, &bytesSent, SafeLocalAllocHandle.Zero, 0, asyncResult == null ? null : asyncResult._pOverlapped, null); if (asyncResult != null && statusCode == Interop.HttpApi.ERROR_SUCCESS && HttpListener.SkipIOCPCallbackOnSuccess) { asyncResult.IOCompleted(statusCode, bytesSent); // IO operation completed synchronously - callback won't be called to signal completion. } } } if (NetEventSource.IsEnabled) { NetEventSource.Info(this, "Call to Interop.HttpApi.HttpSendHttpResponse returned:" + statusCode); } } finally { FreePinnedHeaders(pinnedHeaders); } return(statusCode); }