Пример #1
0
        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);
        }
Пример #2
0
        /// <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);
        }