private static async Task <RTIHttpContent> CreateRequestContentAsync(HttpRequestMessage request, HttpRequestHeaderCollection rtHeaderCollection) { HttpContent content = request.Content; RTIHttpContent rtContent; ArraySegment <byte> buffer; // If we are buffered already, it is more efficient to send the data directly using the buffer with the // WinRT HttpBufferContent class than using HttpStreamContent. This also avoids issues caused by // a design limitation in the System.Runtime.WindowsRuntime System.IO.NetFxToWinRtStreamAdapter. if (content.TryGetBuffer(out buffer)) { rtContent = new RTHttpBufferContent(buffer.Array.AsBuffer(), (uint)buffer.Offset, (uint)buffer.Count); } else { Stream contentStream = await content.ReadAsStreamAsync().ConfigureAwait(false); if (contentStream is RTIInputStream) { rtContent = new RTHttpStreamContent((RTIInputStream)contentStream); } else if (contentStream is MemoryStream) { var memStream = contentStream as MemoryStream; if (memStream.TryGetBuffer(out buffer)) { rtContent = new RTHttpBufferContent(buffer.Array.AsBuffer(), (uint)buffer.Offset, (uint)buffer.Count); } else { byte[] byteArray = memStream.ToArray(); rtContent = new RTHttpBufferContent(byteArray.AsBuffer(), 0, (uint)byteArray.Length); } } else { rtContent = new RTHttpStreamContent(contentStream.AsInputStream()); } } // RTHttpBufferContent constructor automatically adds a Content-Length header. RTHttpStreamContent does not. // Clear any 'Content-Length' header added by the RTHttp*Content objects. We need to clear that now // and decide later whether we need 'Content-Length' or 'Transfer-Encoding: chunked' headers based on the // .NET HttpRequestMessage and Content header collections. rtContent.Headers.ContentLength = null; // Deal with conflict between 'Content-Length' vs. 'Transfer-Encoding: chunked' semantics. // Desktop System.Net allows both headers to be specified but ends up stripping out // 'Content-Length' and using chunked semantics. The WinRT APIs throw an exception so // we need to manually strip out the conflicting header to maintain app compatibility. if (request.Headers.TransferEncodingChunked.HasValue && request.Headers.TransferEncodingChunked.Value) { content.Headers.ContentLength = null; } else { // Trigger delayed header generation via TryComputeLength. This code is needed due to an outstanding // bug in HttpContentHeaders.ContentLength. See GitHub Issue #5523. content.Headers.ContentLength = content.Headers.ContentLength; } foreach (KeyValuePair <string, IEnumerable <string> > headerPair in content.Headers) { foreach (string value in headerPair.Value) { if (!rtContent.Headers.TryAppendWithoutValidation(headerPair.Key, value)) { // rtContent headers are restricted to a white-list of allowed headers, while System.Net.HttpClient's content headers // will allow custom headers. If something is not successfully added to the content headers, try adding them to the standard headers. bool success = rtHeaderCollection.TryAppendWithoutValidation(headerPair.Key, value); Debug.Assert(success); } } } return(rtContent); }
/// <summary>Copies a source stream to a LimitMemoryStream by writing directly to the LimitMemoryStream's buffer.</summary> /// <param name="source">The source stream from which to copy.</param> /// <param name="destination">The destination LimitMemoryStream to write to.</param> /// <param name="bufferSize">The size of the buffer to allocate if one needs to be allocated.</param> /// <param name="disposeSource">Whether to dispose of the source stream after the copy has finished successfully.</param> private static async Task CopyAsyncToPreSizedLimitMemoryStream(Stream source, HttpContent.LimitMemoryStream destination, int bufferSize, bool disposeSource) { // When a LimitMemoryStream is constructed to represent a response with a particular ContentLength, its // Capacity is set to that amount in order to pre-size it. We can take advantage of that in this copy // by handing the destination's pre-sized underlying byte[] to the source stream for it to read into // rather than creating a temporary buffer, copying from the source into that, and then copying again // from the buffer into the LimitMemoryStream. long capacity = destination.Capacity; Debug.Assert(capacity > 0, "Caller should have checked that there's capacity"); // Get the initial length of the stream. When the length of a LimitMemoryStream is increased, the newly available // space is zero-filled, either due to allocating a new array or due to an explicit clear. As a result, we can't // write into the array directly and then increase the length to the right size afterward, as doing so will overwrite // all of the data newly written. Instead, we need to increase the length to the capacity, write in our data, and // then subsequently trim back the length to the end of the written data. long startingLength = destination.Length; if (startingLength < capacity) { destination.SetLength(capacity); } int bytesRead; try { // Get the LimitMemoryStream's buffer. ArraySegment<byte> entireBuffer; bool gotBuffer = destination.TryGetBuffer(out entireBuffer); Debug.Assert(gotBuffer, "Should only be in CopyAsyncToMemoryStream if we were able to get the buffer"); Debug.Assert(entireBuffer.Offset == 0, "LimitMemoryStream's are only constructed with a 0-offset"); Debug.Assert(entireBuffer.Count == entireBuffer.Array.Length, $"LimitMemoryStream's buffer count {entireBuffer.Count} should be the same as its length {entireBuffer.Array.Length}"); // While there's space remaining in the destination buffer, do another read to try to fill it. // Each time we read successfully, we update the position of the destination stream to be // at the end of the data read. int spaceRemaining = (int)(entireBuffer.Array.Length - destination.Position); while (spaceRemaining > 0) { // Read into the buffer bytesRead = await source.ReadAsync(entireBuffer.Array, (int)destination.Position, spaceRemaining).ConfigureAwait(false); if (bytesRead == 0) { DisposeSource(disposeSource, source); return; } destination.Position += bytesRead; spaceRemaining -= bytesRead; } } finally { // Now that we're done reading directly into the buffer, if we previously increased the length // of the stream, set it be at the end of the data read. if (startingLength < capacity) { destination.SetLength(destination.Position); } } // A typical case will be that we read exactly the amount requested. This means that the next // read will likely return 0 bytes, but we need to try to do the read to know that, which means // we need a buffer to read into. Use a cached single-byte array to do a read for 1-byte. // Ideally this read returns 0, and we're done. byte[] singleByteArray = RentCachedSingleByteArray(); bytesRead = await source.ReadAsync(singleByteArray, 0, 1).ConfigureAwait(false); if (bytesRead == 0) { ReturnCachedSingleByteArray(singleByteArray); DisposeSource(disposeSource, source); return; } // The read actually returned data, which means there was more data available then // the capacity of the LimitMemoryStream. This is likely an error condition, but // regardless we need to finish the copy. First, we write out the byte we read... await destination.WriteAsync(singleByteArray, 0, 1).ConfigureAwait(false); ReturnCachedSingleByteArray(singleByteArray); // ...then we fall back to doing the normal read/write loop. await CopyAsyncAnyStreamToAnyStreamCore(source, destination, bufferSize, disposeSource).ConfigureAwait(false); }