1 //------------------------------------------------------------------------------
2 // <copyright file="_ConnectStream.cs" company="Microsoft">
3 // Copyright (c) Microsoft Corporation. All rights reserved.
5 //------------------------------------------------------------------------------
8 using System.Diagnostics;
10 using System.Net.Security;
11 using System.Net.Sockets;
12 using System.Runtime.InteropServices;
13 using System.Threading;
14 using System.Security.Authentication.ExtendedProtection;
15 using System.Security.Permissions;
16 using System.ComponentModel;
17 using System.Threading.Tasks;
18 using System.Configuration;
19 using System.Globalization;
21 internal struct WriteHeadersCallbackState{
22 internal HttpWebRequest request;
23 internal ConnectStream stream;
25 internal WriteHeadersCallbackState(HttpWebRequest request, ConnectStream stream){
26 this.request = request;
33 ConnectStream - a stream interface to a Connection object.
35 This class implements the Stream interface, as well as a
36 WriteHeaders call. Inside this stream we handle details like
37 chunking, dechunking and tracking of ContentLength. To write
38 or read data, we call a method on the connection object. The
39 connection object is responsible for protocol stuff that happens
40 'below' the level of the HTTP request, for example MUX or SSL
44 internal class ConnectStream : Stream, ICloseEx, IRequestLifetimeTracker
47 internal IAsyncResult _PendingResult;
49 // These would be defined in the IOControlCode enum but we don't want them to be public.
50 private const int ApplyTransportSetting = unchecked((int)0x98000013);
51 private const int QueryTransportSetting = unchecked((int)0x98000014);
53 private static class Nesting {
54 public const int Idle = 0;
55 public const int IoInProgress = 1; // we are doing read or write
56 public const int Closed = 2; // stream was closed if that is done in IoInProgress on write, the write will resume delayed close part.
57 public const int InError = 3; // IO is not allowed due to error stream state
58 public const int InternalIO = 4; // stream is used by us, this is internal error if public IO sees that value
61 private int m_CallNesting; // see Nesting enum for details
62 private ScatterGatherBuffers
63 m_BufferedData; // list of sent buffers in case of resubmit (redirect/authentication).
64 private bool m_SuppressWrite; // don't write data to the connection, only buffer it
65 private bool m_BufferOnly; // don't write data to the connection, only buffer it
66 private long m_BytesLeftToWrite; // Total bytes left to be written.
67 private int m_BytesAlreadyTransferred; // Bytes already read/written in the current operation.
68 private Connection m_Connection; // Connection for I/O.
69 private byte[] m_ReadBuffer; // Read buffer for read stream.
70 private int m_ReadOffset; // Offset into m_ReadBuffer.
71 private int m_ReadBufferSize; // Bytes left in m_ReadBuffer.
72 private long m_ReadBytes; // Total bytes to read on stream, -1 for read to end.
73 private bool m_Chunked; // True if we're doing chunked read.
74 private int m_DoneCalled; // 0 at init, 1 after we've called Read/Write Done
75 private int m_ShutDown; // 0 at init, 1 after we've called Complete
76 private Exception m_ErrorException; // non-null if we've seen an error on this connection.
77 private bool m_ChunkEofRecvd; // True, if we've seen an EOF, or reached a EOF state for no more reads
78 private ChunkParser m_ChunkParser; // Helper object used for parsing chunked responses.
80 private HttpWriteMode m_HttpWriteMode;
82 private int m_ReadTimeout; // timeout in ms for reads
83 private int m_WriteTimeout; // timeout in ms for writes
85 private RequestLifetimeSetter m_RequestLifetimeSetter;
87 private const long c_MaxDrainBytes = 64 * 1024; // 64 K - greater than, we should just close the connection
89 // These two must not be static because the socket will use them when caching the user context.
90 private readonly AsyncCallback m_ReadCallbackDelegate;
91 private readonly AsyncCallback m_WriteCallbackDelegate;
92 private static readonly AsyncCallback m_WriteHeadersCallback = new AsyncCallback(WriteHeadersCallback);
94 // Special value indicating that an asynchronous read operation is intentionally zero-length.
95 private static readonly object ZeroLengthRead = new object();
97 private HttpWebRequest m_Request;
99 private static volatile int responseDrainTimeoutMilliseconds = Timeout.Infinite;
100 private const int defaultResponseDrainTimeoutMilliseconds = 500;
101 private const string responseDrainTimeoutAppSetting = "responseDrainTimeout";
104 // Timeout - timeout in ms for sync reads & writes, passed in HttpWebRequest
107 public override bool CanTimeout {
111 public override int ReadTimeout {
113 return m_ReadTimeout;
116 if (value<=0 && value!=System.Threading.Timeout.Infinite) {
117 throw new ArgumentOutOfRangeException("value", SR.GetString(SR.net_io_timeout_use_gt_zero));
119 m_ReadTimeout = value;
123 public override int WriteTimeout {
125 return m_WriteTimeout;
129 if (value<=0 && value!=System.Threading.Timeout.Infinite) {
130 throw new ArgumentOutOfRangeException("value", SR.GetString(SR.net_io_timeout_use_gt_zero));
132 m_WriteTimeout = value;
136 // We will be done with this stream/connection after the user finishes uploading (redirected)
137 internal bool FinishedAfterWrite { get; set; }
140 // If IgnoreSocketErrors==true then no data will be sent to the wire
142 private bool m_IgnoreSocketErrors;
143 internal bool IgnoreSocketErrors {
145 return m_IgnoreSocketErrors;
150 // If the KeepAlive=true then we must be prepares for a write socket errors trying to flush the body
151 // If the KeepAlive=false then we should cease body writing as the connection is probably dead
152 // If fatal=true then the connection is dead due to IO fault (discovered during read), throw IO exception
154 // m_IgnoreSocketErrors and m_ThrowSocketError are mostly for a write type of streams.
155 // However a response read stream may have this member set when draning a response on resubmit.
157 // This this isn't synchronized, we also check after receiving an exception from the transport whether these have been set
158 // and take them into account if they have (on writes).
159 private bool m_ErrorResponseStatus;
160 internal void ErrorResponseNotify(bool isKeepAlive) {
161 m_ErrorResponseStatus = true;
162 m_IgnoreSocketErrors |= !isKeepAlive;
163 GlobalLog.Print((WriteStream?"Write-":"Read-") + "ConnectStream#"+ ValidationHelper.HashString(this) + "::Got notification on an Error Response, m_IgnoreSocketErrors:" + m_IgnoreSocketErrors);
166 // This means we should throw a connection closed exception from now on (write only).
167 // It's unclear whether this needs to be better synchronized with m_ErrorResponseStatus, such as if ErrorResponseNotify
168 // were called (asynchronously) while a m_ErrorException was already set.
169 internal void FatalResponseNotify()
171 if (m_ErrorException == null)
173 Interlocked.CompareExchange<Exception>(ref m_ErrorException, new IOException(SR.GetString(SR.net_io_readfailure, SR.GetString(SR.net_io_connectionclosed))), null);
175 m_ErrorResponseStatus = false;
176 GlobalLog.Print((WriteStream ? "Write-" : "Read-") + "ConnectStream#" + ValidationHelper.HashString(this) + "::Got notification on a Fatal Response");
180 Write Constructor for this class. This is the write constructor;
181 it takes as a parameter the amount of entity body data to be written,
182 with a value of -1 meaning to write it out as chunked. The other
183 parameter is the Connection of which we'll be writing.
185 Right now we use the DefaultBufferSize for the stream. In
186 the future we'd like to pass a 0 and have the stream be
187 unbuffered for write.
191 Conn - Connection for this stream.
192 BytesToWrite - Total bytes to be written, or -1
193 if we're doing chunked encoding.
201 public ConnectStream(Connection connection, HttpWebRequest request) {
202 GlobalLog.Print("ConnectStream#" + ValidationHelper.HashString(this) + "::.ctor(Write)");
203 m_Connection = connection;
204 m_ReadTimeout = m_WriteTimeout = System.Threading.Timeout.Infinite;
206 // we need to save a reference to the request for two things
207 // 1. In case of buffer-only we kick in actual submition when the stream is closed by a user
208 // 2. In case of write stream abort() we notify the request so the response stream is handled properly
211 m_HttpWriteMode = request.HttpWriteMode;
213 GlobalLog.Assert(m_HttpWriteMode != HttpWriteMode.Unknown, "ConnectStream#{0}::.ctor()|HttpWriteMode:{1}", ValidationHelper.HashString(this), m_HttpWriteMode);
214 m_BytesLeftToWrite = m_HttpWriteMode==HttpWriteMode.ContentLength ? request.ContentLength : -1;
215 if (request.HttpWriteMode==HttpWriteMode.Buffer) {
217 EnableWriteBuffering();
219 GlobalLog.Print("ConnectStream#" + ValidationHelper.HashString(this) + "::.ctor() Connection:" + ValidationHelper.HashString(m_Connection) + " BytesToWrite:" + BytesLeftToWrite);
221 m_ReadCallbackDelegate = new AsyncCallback(ReadCallback);
222 m_WriteCallbackDelegate = new AsyncCallback(WriteCallback);
227 Read constructor for this class. This constructor takes in
228 the connection and some information about a buffer that already
229 contains data. Reads from this stream will read first from the
230 buffer, and after that is exhausted will read from the connection.
232 We also take in a size of bytes to read, or -1 if we're to read
233 to connection close, and a flag indicating whether or now
238 Conn - Connection for this stream.
239 buffer - Initial buffer to read from.
240 offset - offset into buffer to start reading.
241 size - number of bytes in buffer to read.
242 readSize - Number of bytes allowed to be read from
243 the stream, -1 for read to connection
245 chunked - True if we're doing chunked decoding.
253 public ConnectStream(Connection connection, byte[] buffer, int offset, int bufferCount, long readCount, bool chunked, HttpWebRequest request)
255 GlobalLog.Print("ConnectStream#" + ValidationHelper.HashString(this) + "::.ctor(Read)");
256 if(Logging.On)Logging.PrintInfo(Logging.Web, this, "ConnectStream", SR.GetString(SR.net_log_buffered_n_bytes, readCount));
258 m_ReadBytes = readCount;
259 m_ReadTimeout = m_WriteTimeout = System.Threading.Timeout.Infinite;
261 m_Connection = connection;
265 m_ChunkParser = new ChunkParser(m_Connection, buffer, offset, bufferCount,
266 request.MaximumResponseHeadersLength * 1024);
270 m_ReadBuffer = buffer;
271 m_ReadOffset = offset;
272 m_ReadBufferSize = bufferCount;
276 // A request reference is used to verify (by the connection class) that this request should start a next one on Close.
279 GlobalLog.Print("ConnectStream#" + ValidationHelper.HashString(this) + "::.ctor() Connection:" + ValidationHelper.HashString(m_Connection) +
280 " m_ReadOffset:" + m_ReadOffset + " m_ReadBufferSize: " + m_ReadBufferSize +
281 " ContentLength: " + m_ReadBytes + " m_Chunked:" + m_Chunked.ToString());
283 m_ReadCallbackDelegate = new AsyncCallback(ReadCallback);
284 m_WriteCallbackDelegate = new AsyncCallback(WriteCallback);
287 internal void SwitchToContentLength(){
288 m_HttpWriteMode = HttpWriteMode.ContentLength;
291 internal bool SuppressWrite {
294 return m_SuppressWrite;
298 m_SuppressWrite = value;
302 internal Connection Connection {
308 internal bool BufferOnly {
314 internal ScatterGatherBuffers BufferedData {
316 return m_BufferedData;
319 m_BufferedData = value;
323 private bool WriteChunked {
325 return m_HttpWriteMode==HttpWriteMode.Chunked;
329 internal long BytesLeftToWrite {
331 return m_BytesLeftToWrite;
334 m_BytesLeftToWrite = value;
338 // True if this is a write stream.
341 return m_HttpWriteMode != HttpWriteMode.Unknown;
345 internal bool IsPostStream {
347 return m_HttpWriteMode != HttpWriteMode.None;
353 ErrorInStream - indicates an exception was caught
354 internally due to a stream error, and that I/O
355 operations should not continue
359 Returns: True if there is an error
363 internal bool ErrorInStream {
365 return m_ErrorException!=null;
371 CallDone - calls out to the Connection that spawned this
372 Stream (using the DoneRead/DoneWrite method).
373 If the Connection specified that we don't need to
374 do this, or if we've already done this already, then
382 internal void CallDone()
386 private void CallDone(ConnectionReturnResult returnResult)
388 GlobalLog.Enter("ConnectStream#" + ValidationHelper.HashString(this) + "::CallDone");
389 if ( Interlocked.Increment( ref m_DoneCalled) == 1 )
394 GlobalLog.DebugRemoveRequest(m_Request);
396 if (returnResult == null) {
397 //readstartnextrequest will call setresponses internally.
401 int leftoverBufferOffset;
402 int leftoverBufferSize;
403 byte[] leftoverBuffer;
405 if (m_ChunkParser.TryGetLeftoverBytes(out leftoverBuffer, out leftoverBufferOffset,
406 out leftoverBufferSize))
408 m_Connection.SetLeftoverBytes(leftoverBuffer, leftoverBufferOffset, leftoverBufferSize);
411 m_Connection.ReadStartNextRequest(m_Request, ref returnResult);
414 ConnectionReturnResult.SetResponses(returnResult);
419 m_Request.WriteCallDone(this, returnResult);
422 GlobalLog.Leave("ConnectStream#" + ValidationHelper.HashString(this) + "::CallDone");
425 internal void ProcessWriteCallDone(ConnectionReturnResult returnResult)
427 GlobalLog.Print("ConnectStream#" + ValidationHelper.HashString(this) + "::ProcessWriteCallDone()");
430 if (returnResult == null) {
431 m_Connection.WriteStartNextRequest(m_Request, ref returnResult);
433 // If the request is Sync, then we do our Read here for data
434 if (!m_Request.Async)
436 object syncReaderResult = m_Request.ConnectionReaderAsyncResult.InternalWaitForCompletion();
438 //we should only do a syncread if we didn't already read the response
439 //via poll when we handed back the request stream
440 if (syncReaderResult == null && m_Request.NeedsToReadForResponse)
442 // Remove once mixed sync/async requests are supported.
443 using (GlobalLog.SetThreadKind(ThreadKinds.Sync))
446 m_Connection.SyncRead(m_Request, true, false);
450 m_Request.NeedsToReadForResponse = true;
453 ConnectionReturnResult.SetResponses(returnResult);
456 // This will decrement the response window on the write side AND may
457 // result in either immediate or delayed processing of a response for the m_Request instance
458 if (IsPostStream || m_Request.Async)
459 m_Request.CheckWriteSideResponseProcessing();
463 internal bool IsClosed {
465 return m_ShutDown != 0;
471 Read property for this class. We return the readability of
472 this stream. This is a read only property.
476 Returns: True if this is a read stream, false otherwise.
480 public override bool CanRead {
482 return !WriteStream && !IsClosed;
488 Seek property for this class. Since this stream is not
489 seekable, we just return false. This is a read only property.
497 public override bool CanSeek {
505 CanWrite property for this class. We return the writeability of
506 this stream. This is a read only property.
510 Returns: True if this is a write stream, false otherwise.
514 public override bool CanWrite {
516 return WriteStream && !IsClosed;
523 Length property for this class. Since we don't support seeking,
524 this property just throws a NotSupportedException.
528 Returns: Throws exception.
532 public override long Length {
534 throw new NotSupportedException(SR.GetString(SR.net_noseek));
540 Position property for this class. Since we don't support seeking,
541 this property just throws a NotSupportedException.
545 Returns: Throws exception.
549 public override long Position {
551 throw new NotSupportedException(SR.GetString(SR.net_noseek));
555 throw new NotSupportedException(SR.GetString(SR.net_noseek));
562 Eof property to indicate when the read is no longer allowed,
563 because all data has been already read from socket.
567 Returns: true/false depending on whether we are complete
576 else if (m_Chunked) {
577 return m_ChunkEofRecvd;
579 else if (m_ReadBytes == 0) {
582 else if (m_ReadBytes == -1) {
583 return(m_DoneCalled > 0 && m_ReadBufferSize <= 0);
592 Uses an old Stream to resubmit buffered data using the current
593 stream, this is used in cases of POST, or authentication,
594 where we need to buffer upload data so that it can be resubmitted
598 OldStream - Old Stream that was previously used
607 internal void ResubmitWrite(ConnectStream oldStream, bool suppressWrite) {
608 GlobalLog.Enter("ConnectStream#" + ValidationHelper.HashString(this) + "::ResubmitWrite", ValidationHelper.HashString(oldStream));
609 GlobalLog.ThreadContract(ThreadKinds.Sync, "ConnectStream#" + ValidationHelper.HashString(this) + "::ResubmitWrite");
618 // we're going to resubmit
621 Interlocked.CompareExchange(ref m_CallNesting, Nesting.InternalIO, Nesting.Idle);
622 GlobalLog.Print("ConnectStream#" + ValidationHelper.HashString(this) + "::ResubmitWrite() Inc: " + m_CallNesting.ToString());
624 GlobalLog.Print("ConnectStream#" + ValidationHelper.HashString(this) + "::ResubmitWrite(), callNesting : " + m_CallNesting.ToString() + " IsClosed = " + IsClosed);
626 // no need to buffer here:
627 // we're already resubmitting buffered data give it to the connection to put it on the wire again
628 // we set BytesLeftToWrite to 0 'cause even on failure there can be no recovery,
629 // so just leave it to IOError() to clean up and don't call ResubmitWrite()
631 ScatterGatherBuffers bufferedData = oldStream.BufferedData;
632 SafeSetSocketTimeout(SocketShutdown.Send);
635 m_Connection.Write(bufferedData);
638 // we have the data buffered, but we still want to chunk.
640 // first set this to disable Close() from sending a chunk terminator.
641 GlobalLog.Assert(m_HttpWriteMode != HttpWriteMode.None, "ConnectStream#{0}::ResubmitWrite()|m_HttpWriteMode == HttpWriteMode.None", ValidationHelper.HashString(this));
642 m_HttpWriteMode = HttpWriteMode.ContentLength;
644 if (bufferedData.Length==0) {
645 m_Connection.Write(NclConstants.ChunkTerminator, 0, NclConstants.ChunkTerminator.Length);
648 int chunkHeaderOffset = 0;
649 byte[] chunkHeaderBuffer = GetChunkHeader(bufferedData.Length, out chunkHeaderOffset);
650 BufferOffsetSize[] dataBuffers = bufferedData.GetBuffers();
651 BufferOffsetSize[] buffers = new BufferOffsetSize[dataBuffers.Length + 3];
652 buffers[0] = new BufferOffsetSize(chunkHeaderBuffer, chunkHeaderOffset, chunkHeaderBuffer.Length - chunkHeaderOffset, false);
654 foreach (BufferOffsetSize buffer in dataBuffers) {
655 buffers[++index] = buffer;
657 buffers[++index] = new BufferOffsetSize(NclConstants.CRLF, 0, NclConstants.CRLF.Length, false);
658 buffers[++index] = new BufferOffsetSize(NclConstants.ChunkTerminator, 0, NclConstants.ChunkTerminator.Length, false);
660 SplitWritesState splitState = new SplitWritesState(buffers);
662 BufferOffsetSize[] sendBuffers = splitState.GetNextBuffers();
663 while(sendBuffers != null){
664 m_Connection.MultipleWrite(sendBuffers);
665 sendBuffers = splitState.GetNextBuffers();
669 if(Logging.On && bufferedData.GetBuffers() != null) {
670 foreach (BufferOffsetSize bufferOffsetSize in bufferedData.GetBuffers()) {
671 if (bufferOffsetSize == null) {
672 Logging.Dump(Logging.Web, this, "ResubmitWrite", null, 0, 0);
675 Logging.Dump(Logging.Web, this, "ResubmitWrite", bufferOffsetSize.Buffer, bufferOffsetSize.Offset, bufferOffsetSize.Size);
679 GlobalLog.Print("ConnectStream#" + ValidationHelper.HashString(this) + "::ResubmitWrite() sent:" + bufferedData.Length.ToString() );
681 catch (Exception exception)
683 if (NclUtilities.IsFatal(exception)) throw;
686 WebException we = new WebException(NetRes.GetWebStatusString("net_connclosed", WebExceptionStatus.SendFailure),
687 WebExceptionStatus.SendFailure,
688 WebExceptionInternalStatus.RequestFatal,
693 Interlocked.CompareExchange(ref m_CallNesting, Nesting.Idle, Nesting.InternalIO);
694 GlobalLog.Print("ConnectStream#" + ValidationHelper.HashString(this) + "::ResubmitWrite(), callNesting : " + m_CallNesting.ToString() + " IsClosed = " + IsClosed);
696 m_BytesLeftToWrite = 0;
698 GlobalLog.Leave("ConnectStream#" + ValidationHelper.HashString(this) + "::ResubmitWrite", BytesLeftToWrite.ToString());
703 // called by HttpWebRequest if AllowWriteStreamBuffering is true on that instance
705 internal void EnableWriteBuffering() {
706 GlobalLog.Assert(WriteStream, "ConnectStream#{0}::EnableWriteBuffering()|!WriteStream", ValidationHelper.HashString(this));
707 if (BufferedData==null) {
708 // create stream on demand, only if needed
709 GlobalLog.Print("ConnectStream#" + ValidationHelper.HashString(this) + "::EnableWriteBuffering() Write() creating ScatterGatherBuffers WriteChunked:" + WriteChunked.ToString() + " BytesLeftToWrite:" + BytesLeftToWrite.ToString());
712 BufferedData = new ScatterGatherBuffers();
716 BufferedData = new ScatterGatherBuffers(BytesLeftToWrite);
722 FillFromBufferedData - This fills in a buffer from data that we have buffered.
724 This method pulls out the buffered data that may have been provided as
725 excess actual data from the header parsing
729 buffer - Buffer to read into.
730 offset - Offset in buffer to read into.
731 size - Size in bytes to read.
734 Number of bytes read.
737 internal int FillFromBufferedData(byte [] buffer, ref int offset, ref int size ) {
739 // if there's no stuff in our read buffer just return 0
741 if (m_ReadBufferSize == 0) {
746 // There's stuff in our read buffer. Figure out how much to take,
747 // which is the minimum of what we have and what we're to read,
750 int BytesTransferred = Math.Min(size, m_ReadBufferSize);
752 GlobalLog.Print("ConnectStream#" + ValidationHelper.HashString(this) + "::FillFromBufferedData() Filling bytes: " + BytesTransferred.ToString());
761 // Update our internal read buffer state with what we took.
763 m_ReadOffset += BytesTransferred;
764 m_ReadBufferSize -= BytesTransferred;
766 // If the read buffer size has gone to 0, null out our pointer
767 // to it so maybe it'll be garbage-collected faster.
769 if (m_ReadBufferSize == 0) {
773 // Update what we're to read and the caller's offset.
775 size -= BytesTransferred;
776 offset += BytesTransferred;
778 return BytesTransferred;
784 This function write data to the network. If we were given a definite
785 content length when constructed, we won't write more than that amount
786 of data to the network. If the caller tries to do that, we'll throw
787 an exception. If we're doing chunking, we'll chunk it up before
788 sending to the connection.
793 buffer - buffer to write.
794 offset - offset in buffer to write from.
795 size - size in bytes to write.
801 public override void Write(byte[] buffer, int offset, int size) {
803 using (GlobalLog.SetThreadKind(ThreadKinds.User | ThreadKinds.Sync)) {
805 if (Logging.On) Logging.Enter(Logging.Web, this, "Write", "");
807 // Basic parameter validation
810 throw new NotSupportedException(SR.GetString(SR.net_readonlystream));
813 throw new ArgumentNullException("buffer");
815 if (offset<0 || offset>buffer.Length) {
816 throw new ArgumentOutOfRangeException("offset");
818 if (size<0 || size>buffer.Length-offset) {
819 throw new ArgumentOutOfRangeException("size");
822 if(Logging.On) Logging.Dump(Logging.Web, this, "Write", buffer, offset, size);
824 InternalWrite(false, buffer, offset, size, null, null );
826 if(Logging.On)Logging.Exit(Logging.Web, this, "Write", "");
835 BeginWrite - Does async write to the Stream
837 Splits the operation into two outcomes, for the
838 non-chunking case, we calculate size to write,
839 then call BeginWrite on the Connection directly,
840 and then we're finish, for the Chunked case,
841 we procede with use two callbacks to continue the
842 chunking after the first write, and then second write.
843 In order that all of the Chunk data/header/terminator,
844 in the correct format are sent.
848 buffer - Buffer to write into.
849 offset - Offset in buffer to write into.
850 size - Size in bytes to write.
851 callback - the callback to be called on result
852 state - object to be passed to callback
855 IAsyncResult - the async result
860 [HostProtection(ExternalThreading=true)]
861 public override IAsyncResult BeginWrite(byte[] buffer, int offset, int size, AsyncCallback callback, object state ) {
863 using (GlobalLog.SetThreadKind(ThreadKinds.User | ThreadKinds.Async)) {
865 GlobalLog.Print("ConnectStream#" + ValidationHelper.HashString(this) + "::BeginWrite " + ValidationHelper.HashString(m_Connection) + ", " + offset.ToString() + ", " + size.ToString());
866 if(Logging.On)Logging.Enter(Logging.Web, this, "BeginWrite", "");
868 // Basic parameter validation
871 throw new NotSupportedException(SR.GetString(SR.net_readonlystream));
874 throw new ArgumentNullException("buffer");
876 if (offset<0 || offset>buffer.Length) {
877 throw new ArgumentOutOfRangeException("offset");
879 if (size<0 || size>buffer.Length-offset) {
880 throw new ArgumentOutOfRangeException("size");
883 if (Logging.On) Logging.Dump(Logging.Web, this, "BeginWrite", buffer, offset, size);
885 IAsyncResult result = InternalWrite(true, buffer, offset, size, callback, state);
886 if(Logging.On)Logging.Exit(Logging.Web, this, "BeginWrite", result);
894 // Handles either async or sync Writing for *public* stream API
896 private IAsyncResult InternalWrite(bool async, byte[] buffer, int offset, int size, AsyncCallback callback, object state ) {
898 // if we have a stream error, or we've already shut down this socket
899 // then we must prevent new BeginRead/BeginWrite's from getting
900 // submited to the socket, since we've already closed the stream.
903 GlobalLog.Print("ConnectStream#" + ValidationHelper.HashString(this) + "::InternalWrite() throwing:" + m_ErrorException.ToString());
904 throw m_ErrorException;
907 if (IsClosed && !IgnoreSocketErrors) {
908 GlobalLog.Print("ConnectStream#" + ValidationHelper.HashString(this) + "::InternalWrite() throwing");
909 throw new WebException(
910 NetRes.GetWebStatusString("net_requestaborted", WebExceptionStatus.ConnectionClosed),
911 WebExceptionStatus.ConnectionClosed);
914 if (m_Request.Aborted && !IgnoreSocketErrors) {
915 GlobalLog.Print("ConnectStream#" + ValidationHelper.HashString(this) + "::InternalWrite() throwing");
916 throw new WebException(
917 NetRes.GetWebStatusString("net_requestaborted", WebExceptionStatus.RequestCanceled),
918 WebExceptionStatus.RequestCanceled);
921 int nesting = Interlocked.CompareExchange(ref m_CallNesting, Nesting.IoInProgress, Nesting.Idle);
922 GlobalLog.Print((async?"Async ":"") + "InternalWrite() In: callNesting : " + nesting.ToString());
923 if (nesting != Nesting.Idle && nesting != Nesting.Closed)
925 throw new NotSupportedException(SR.GetString(SR.net_no_concurrent_io_allowed));
929 // buffer data to the ScatterGatherBuffers
930 // regardles of chunking, we buffer the data as if we were not chunking
931 // and on resubmit, we don't bother chunking.
933 if (BufferedData!=null && size != 0 && (m_Request.ContentLength != 0 || !IsPostStream || !m_Request.NtlmKeepAlive)) {
935 // if we don't need to, we shouldn't send data on the wire as well
936 // but in this case we gave a stream to the user so we have transport
938 BufferedData.Write(buffer, offset, size);
941 LazyAsyncResult asyncResult = null;
942 bool completeSync = false;
945 if (size == 0 || BufferOnly || m_SuppressWrite || IgnoreSocketErrors)
948 // We're not putting this data on the wire, then we're done
950 if(m_SuppressWrite && m_BytesLeftToWrite > 0 && size > 0)
952 m_BytesLeftToWrite -= size;
955 GlobalLog.Print("ConnectStream#" + ValidationHelper.HashString(this) + "::InternalWrite() ----ing: size==0 || BufferOnly || IgnoreSocketErrors= " + (size==0) + BufferOnly + IgnoreSocketErrors);
957 asyncResult = new LazyAsyncResult(this, state, callback);
962 else if (WriteChunked) {
964 // We're chunking. Write the chunk header out first,
965 // then the data, then a CRLF.
966 // for this we'll use BeginMultipleSend();
968 int chunkHeaderOffset = 0;
969 byte[] chunkHeaderBuffer = GetChunkHeader(size, out chunkHeaderOffset);
971 BufferOffsetSize[] buffers;
972 GlobalLog.Print("ConnectStream#" + ValidationHelper.HashString(this) + "::InternalWrite() m_ErrorResponseStatus:" + m_ErrorResponseStatus);
974 if (m_ErrorResponseStatus) {
975 //if we already got a (>200) response, then just terminate chunking and
976 //switch to simple buffering (if any)
977 GlobalLog.Print("ConnectStream#" + ValidationHelper.HashString(this) + "::InternalWrite() setting m_IgnoreSocketErrors to True (was:" + m_IgnoreSocketErrors + ") sending chunk terminator");
978 m_IgnoreSocketErrors = true;
979 buffers = new BufferOffsetSize[1];
980 buffers[0] = new BufferOffsetSize(NclConstants.ChunkTerminator, 0, NclConstants.ChunkTerminator.Length, false);
983 buffers = new BufferOffsetSize[3];
984 buffers[0] = new BufferOffsetSize(chunkHeaderBuffer, chunkHeaderOffset, chunkHeaderBuffer.Length - chunkHeaderOffset, false);
985 buffers[1] = new BufferOffsetSize(buffer, offset, size, false);
986 buffers[2] = new BufferOffsetSize(NclConstants.CRLF, 0, NclConstants.CRLF.Length, false);
989 asyncResult = (async) ? new NestedMultipleAsyncResult(this, state, callback, buffers) : null;
992 // after setting up the buffers and error checking do the async Write Call
997 m_Connection.BeginMultipleWrite(buffers, m_WriteCallbackDelegate, asyncResult);
1000 SafeSetSocketTimeout(SocketShutdown.Send);
1001 m_Connection.MultipleWrite(buffers);
1005 catch (Exception exception) {
1006 // IgnoreSocketErrors can be set at any time - need to check it again.
1007 if (IgnoreSocketErrors && !NclUtilities.IsFatal(exception))
1009 GlobalLog.Print("ConnectStream#" + ValidationHelper.HashString(this) + "::InternalWrite() ----ing: IgnoreSocketErrors set after throw.");
1012 completeSync = true;
1017 if (m_Request.Aborted && (exception is IOException || exception is ObjectDisposedException)) {
1018 GlobalLog.Print("ConnectStream#" + ValidationHelper.HashString(this) + "::InternalWrite() throwing");
1019 throw new WebException(
1020 NetRes.GetWebStatusString("net_requestaborted", WebExceptionStatus.RequestCanceled),
1021 WebExceptionStatus.RequestCanceled);
1024 nesting = Nesting.InError;
1026 if (NclUtilities.IsFatal(exception))
1028 m_ErrorResponseStatus = false;
1033 if (m_ErrorResponseStatus) {
1034 // We already got a error response, hence server could drop the connection,
1035 // Here we are recovering for future (optional) resubmit ...
1036 m_IgnoreSocketErrors = true;
1037 GlobalLog.Print("ConnectStream#" + ValidationHelper.HashString(this) + "::InternalWrite() IGNORE write fault");
1040 completeSync = true;
1044 // Note we could ---- this since receive callback is already posted and
1045 // should give us similar failure
1047 GlobalLog.Print("ConnectStream#" + ValidationHelper.HashString(this) + "::InternalWrite() throwing:" + exception.ToString());
1051 GlobalLog.Print("ConnectStream#" + ValidationHelper.HashString(this) + "::InternalWrite chunked");
1056 // We're not chunking. See if we're sending too much; if not,
1057 // go ahead and write it.
1059 asyncResult = (async) ? new NestedSingleAsyncResult(this, state, callback, buffer, offset, size) : null;
1061 if (BytesLeftToWrite != -1) {
1063 // but only check if we aren't writing to an unknown content-length size,
1064 // as we can be buffering.
1066 if (BytesLeftToWrite < (long)size) {
1068 // writing too much data.
1070 GlobalLog.Print("ConnectStream#" + ValidationHelper.HashString(this) + "::InternalWrite()");
1071 throw new ProtocolViolationException(SR.GetString(SR.net_entitytoobig));
1076 // Otherwise update our bytes left to send and send it.
1078 m_BytesLeftToWrite -= (long)size;
1083 // After doing, the m_WriteByte size calculations, and error checking
1084 // here doing the async Write Call
1089 if(m_Request.ContentLength == 0 && IsPostStream) {
1090 m_BytesLeftToWrite -=size;
1091 completeSync = true;
1094 m_BytesAlreadyTransferred = size;
1095 m_Connection.BeginWrite(buffer, offset, size, m_WriteCallbackDelegate, asyncResult);
1099 SafeSetSocketTimeout(SocketShutdown.Send);
1100 //If we are doing the ntlm handshake, contentlength
1101 //could be 0 for the first part, even if there is data
1103 if (m_Request.ContentLength != 0 || !IsPostStream || !m_Request.NtlmKeepAlive) {
1104 m_Connection.Write(buffer, offset, size);
1108 catch (Exception exception) {
1109 // IgnoreSocketErrors can be set at any time - need to check it again.
1110 if (IgnoreSocketErrors && !NclUtilities.IsFatal(exception))
1112 GlobalLog.Print("ConnectStream#" + ValidationHelper.HashString(this) + "::InternalWrite() ----ing: IgnoreSocketErrors set after throw.");
1115 completeSync = true;
1120 if (m_Request.Aborted && (exception is IOException || exception is ObjectDisposedException)) {
1121 GlobalLog.Print("ConnectStream#" + ValidationHelper.HashString(this) + "::InternalWrite() throwing");
1122 throw new WebException(
1123 NetRes.GetWebStatusString("net_requestaborted", WebExceptionStatus.RequestCanceled),
1124 WebExceptionStatus.RequestCanceled);
1127 nesting = Nesting.InError;
1129 if (NclUtilities.IsFatal(exception))
1131 m_ErrorResponseStatus = false;
1136 if (m_ErrorResponseStatus) {
1137 // We already got a error response, hence server could drop the connection,
1138 // Here we are recovering for future (optional) resubmit ...
1139 m_IgnoreSocketErrors = true;
1140 GlobalLog.Print("ConnectStream#" + ValidationHelper.HashString(this) + "::InternWrite() IGNORE write fault");
1143 completeSync = true;
1147 // Note we could ---- this since receive callback is already posted and
1148 // should give us similar failure
1150 GlobalLog.Print("ConnectStream#" + ValidationHelper.HashString(this) + "::InternalWrite() throwing:" + exception.ToString());
1154 GlobalLog.Print("ConnectStream#" + ValidationHelper.HashString(this) + "::InternalWrite");
1159 if (!async || nesting == Nesting.InError || completeSync)
1161 nesting = Interlocked.CompareExchange(ref m_CallNesting, (nesting == Nesting.InError? Nesting.InError: Nesting.Idle), Nesting.IoInProgress);
1162 GlobalLog.Print("InternalWrite() Out callNesting: " + nesting.ToString());
1163 if (nesting == Nesting.Closed)
1165 //send closing bytes
1166 ResumeInternalClose(asyncResult);
1168 else if (completeSync && asyncResult != null)
1170 asyncResult.InvokeCallback();
1180 This is a callback, that is part of the main BeginWrite
1181 code, this is part of the normal transfer code.
1185 asyncResult - IAsyncResult generated from BeginWrite
1192 private void WriteCallback(IAsyncResult asyncResult)
1194 LazyAsyncResult userResult = (LazyAsyncResult) asyncResult.AsyncState;
1195 ((ConnectStream) userResult.AsyncObject).ProcessWriteCallback(asyncResult, userResult);
1198 private void ProcessWriteCallback(IAsyncResult asyncResult, LazyAsyncResult userResult)
1200 Exception userException = null;
1203 NestedSingleAsyncResult castedSingleAsyncResult = userResult as NestedSingleAsyncResult;
1204 if (castedSingleAsyncResult != null)
1207 m_Connection.EndWrite(asyncResult);
1208 if (BytesLeftToWrite != -1) {
1209 // Update our bytes left to send.
1210 m_BytesLeftToWrite -= m_BytesAlreadyTransferred;
1211 m_BytesAlreadyTransferred = 0;
1214 catch (Exception exception) {
1216 userException = exception;
1218 if (NclUtilities.IsFatal(exception))
1220 m_ErrorResponseStatus = false;
1224 if (m_ErrorResponseStatus) {
1225 // We already got a error response, hence server could drop the connection,
1226 // Here we are recovering for future (optional) resubmit ...
1227 m_IgnoreSocketErrors = true;
1228 userException = null;
1229 GlobalLog.Print("ConnectStream#" + ValidationHelper.HashString(this) + "::EndWrite() IGNORE write fault");
1234 NestedMultipleAsyncResult castedMultipleAsyncResult = (NestedMultipleAsyncResult) userResult;
1236 m_Connection.EndMultipleWrite(asyncResult);
1238 catch (Exception exception) {
1240 userException = exception;
1242 if (NclUtilities.IsFatal(exception))
1244 m_ErrorResponseStatus = false;
1248 if (m_ErrorResponseStatus) {
1249 // We already got a error response, hence server could drop the connection,
1250 // Here we are recovering for future (optional) resubmit ...
1251 m_IgnoreSocketErrors = true;
1252 userException = null;
1253 GlobalLog.Print("ConnectStream#" + ValidationHelper.HashString(this) + "::EndWrite() IGNORE write fault");
1260 if (Nesting.Closed == ExchangeCallNesting((userException == null? Nesting.Idle: Nesting.InError), Nesting.IoInProgress))
1262 if (userException != null && m_ErrorException == null)
1264 Interlocked.CompareExchange<Exception>(ref m_ErrorException, userException, null);
1266 ResumeInternalClose(userResult);
1270 userResult.InvokeCallback(userException);
1274 //I need this because doing this within the static w/ "ref stream.m_Callnesting is getting an error.
1275 private int ExchangeCallNesting(int value, int comparand) {
1276 int result = Interlocked.CompareExchange(ref m_CallNesting, value, comparand);
1277 GlobalLog.Print("an AsyncCallback Out callNesting: " + m_CallNesting.ToString());
1282 EndWrite - Finishes off async write of data, just calls into
1283 m_Connection.EndWrite to get the result.
1287 asyncResult - The AsyncResult returned by BeginWrite
1291 public override void EndWrite(IAsyncResult asyncResult) {
1293 using (GlobalLog.SetThreadKind(ThreadKinds.User)) {
1295 GlobalLog.Enter("ConnectStream#" + ValidationHelper.HashString(this) + "::EndWrite");
1296 if(Logging.On)Logging.Enter(Logging.Web, this, "EndWrite", "");
1298 // parameter validation
1300 if (asyncResult==null) {
1301 throw new ArgumentNullException("asyncResult");
1303 LazyAsyncResult castedAsyncResult = asyncResult as LazyAsyncResult;
1305 if (castedAsyncResult==null || castedAsyncResult.AsyncObject!=this) {
1306 throw new ArgumentException(SR.GetString(SR.net_io_invalidasyncresult), "asyncResult");
1308 if (castedAsyncResult.EndCalled) {
1309 throw new InvalidOperationException(SR.GetString(SR.net_io_invalidendcall, "EndWrite"));
1312 castedAsyncResult.EndCalled = true;
1315 // wait & then check for errors
1318 object returnValue = castedAsyncResult.InternalWaitForCompletion();
1320 if (ErrorInStream) {
1321 GlobalLog.LeaveException("ConnectStream#" + ValidationHelper.HashString(this) + "::EndWrite", m_ErrorException);
1322 throw m_ErrorException;
1325 Exception exception = returnValue as Exception;
1326 if (exception!=null) {
1328 if (exception is IOException && m_Request.Aborted) {
1329 GlobalLog.Print("ConnectStream#" + ValidationHelper.HashString(this) + "::InternalWrite() throwing");
1330 throw new WebException(
1331 NetRes.GetWebStatusString("net_requestaborted", WebExceptionStatus.RequestCanceled),
1332 WebExceptionStatus.RequestCanceled);
1336 GlobalLog.LeaveException("ConnectStream#" + ValidationHelper.HashString(this) + "::EndWrite", exception);
1340 GlobalLog.Leave("ConnectStream#" + ValidationHelper.HashString(this) + "::EndWrite");
1341 if(Logging.On)Logging.Exit(Logging.Web, this, "EndWrite", "");
1349 Read - Read from the connection.
1350 ReadWithoutValidation
1352 This method reads from the network, or our internal buffer if there's
1353 data in that. If there's not, we'll read from the network. If we're
1355 doing chunked decoding, we'll decode it before returning from this
1361 buffer - Buffer to read into.
1362 offset - Offset in buffer to read into.
1363 size - Size in bytes to read.
1369 public override int Read([In, Out] byte[] buffer, int offset, int size) {
1371 using (GlobalLog.SetThreadKind(ThreadKinds.User | ThreadKinds.Sync)) {
1373 if (Logging.On) Logging.Enter(Logging.Web, this, "Read", "");
1375 throw new NotSupportedException(SR.GetString(SR.net_writeonlystream));
1378 throw new ArgumentNullException("buffer");
1380 if (offset<0 || offset>buffer.Length) {
1381 throw new ArgumentOutOfRangeException("offset");
1383 if (size<0 || size>buffer.Length-offset) {
1384 throw new ArgumentOutOfRangeException("size");
1386 if (ErrorInStream) {
1387 throw m_ErrorException;
1391 throw new WebException(
1392 NetRes.GetWebStatusString("net_requestaborted", WebExceptionStatus.ConnectionClosed),
1393 WebExceptionStatus.ConnectionClosed);
1396 if (m_Request.Aborted) {
1397 GlobalLog.Print("ConnectStream#" + ValidationHelper.HashString(this) + "::InternalWrite() throwing");
1398 throw new WebException(
1399 NetRes.GetWebStatusString("net_requestaborted", WebExceptionStatus.RequestCanceled),
1400 WebExceptionStatus.RequestCanceled);
1404 // if we fail/hang this call for some reason,
1405 // this Nesting count we be non-0, so that when we
1406 // close this stream, we will abort the socket.
1408 int nesting = Interlocked.CompareExchange(ref m_CallNesting, Nesting.IoInProgress, Nesting.Idle);
1409 GlobalLog.Print("Read() In: callNesting : " + m_CallNesting.ToString());
1411 if (nesting != Nesting.Idle)
1413 throw new NotSupportedException(SR.GetString(SR.net_no_concurrent_io_allowed));
1419 SafeSetSocketTimeout(SocketShutdown.Receive);
1421 catch (Exception exception)
1427 bytesRead = ReadWithoutValidation(buffer, offset, size);
1429 catch (Exception exception)
1431 Win32Exception win32Exception = exception.InnerException as Win32Exception;
1432 if (win32Exception != null && win32Exception.NativeErrorCode == (int)SocketError.TimedOut)
1433 exception = new WebException(SR.GetString(SR.net_timeout), WebExceptionStatus.Timeout);
1437 Interlocked.CompareExchange(ref m_CallNesting, Nesting.Idle, Nesting.IoInProgress);
1438 GlobalLog.Print("Read() Out: callNesting: " + m_CallNesting.ToString());
1440 if(Logging.On && bytesRead>0)Logging.Dump(Logging.Web, this, "Read", buffer, offset, bytesRead);
1441 if(Logging.On)Logging.Exit(Logging.Web, this, "Read", bytesRead);
1450 ReadWithoutValidation - Read from the connection.
1452 Sync version of BeginReadWithoutValidation
1454 This method reads from the network, or our internal buffer if there's
1455 data in that. If there's not, we'll read from the network. If we're
1456 doing chunked decoding, we'll decode it before returning from this
1460 private int ReadWithoutValidation(byte[] buffer, int offset, int size)
1462 return ReadWithoutValidation(buffer, offset, size, true);
1466 // abortOnError parameter is set to false when called from CloseInternal
1468 private int ReadWithoutValidation([In, Out] byte[] buffer, int offset, int size, bool abortOnError)
1470 GlobalLog.Print("int ConnectStream::ReadWithoutValidation()");
1471 GlobalLog.Print("(start)m_ReadBytes = "+m_ReadBytes);
1474 // ********** WARNING - updating logic below should also be updated in BeginReadWithoutValidation *****************
1477 // Figure out how much we should really read.
1480 int bytesToRead = 0;
1483 if (!m_ChunkEofRecvd) {
1486 bytesToRead = m_ChunkParser.Read(buffer, offset, size);
1488 if (bytesToRead == 0) {
1489 m_ChunkEofRecvd = true;
1493 catch (Exception exception) {
1506 // Not doing chunked, so don't read more than is left.
1509 if (m_ReadBytes != -1) {
1510 bytesToRead = (int)Math.Min(m_ReadBytes, (long)size);
1517 // If we're not going to read anything, either because they're
1518 // not asking for anything or there's nothing left, bail
1521 if (bytesToRead == 0 || this.Eof) {
1525 Debug.Assert(!m_Chunked,
1526 "Chunked responses should never get here: Either we go into the chunked-specific code path or the " +
1527 "response is complete (Eof is true).");
1530 bytesToRead = InternalRead(buffer, offset, bytesToRead);
1532 catch (Exception exception) {
1539 GlobalLog.Print("bytesTransferred = "+bytesToRead);
1540 int bytesTransferred = bytesToRead;
1542 bool doneReading = false;
1544 if (bytesTransferred <= 0) {
1545 bytesTransferred = 0;
1548 // We read 0 bytes from the connection, or got an error. This is OK if we're
1549 // reading to end, it's an error otherwise.
1551 if (m_ReadBytes != -1) {
1554 IOError(null, false); // request will be aborted but the user will see EOF on that stream read call
1557 throw m_ErrorException; // CloseInternal will process this case as abnormal
1562 // We're reading to end, and we found the end, by reading 0 bytes
1569 // Not chunking. Update our read bytes state and return what we've read.
1572 if (m_ReadBytes != -1) {
1573 m_ReadBytes -= bytesTransferred;
1575 GlobalLog.Assert(m_ReadBytes >= 0, "ConnectStream: Attempting to read more bytes than available.|m_ReadBytes < 0");
1577 GlobalLog.Print("m_ReadBytes = "+m_ReadBytes);
1579 if (m_ReadBytes < 0)
1580 throw new InternalException(); //
1584 if (m_ReadBytes == 0 || doneReading) {
1585 // We're all done reading, tell the connection that.
1589 // indicate to cache that read completed OK
1595 GlobalLog.Print("bytesTransferred = "+bytesToRead);
1596 GlobalLog.Print("(end)m_ReadBytes = "+m_ReadBytes);
1597 // ********** WARNING - updating logic above should also be updated in BeginReadWithoutValidation and EndReadWithoutValidation *****************
1598 return bytesTransferred;
1604 BeginRead - Read from the connection.
1605 BeginReadWithoutValidation
1607 This method reads from the network, or our internal buffer if there's
1608 data in that. If there's not, we'll read from the network. If we're
1609 doing chunked decoding, we'll decode it before returning from this
1615 buffer - Buffer to read into.
1616 offset - Offset in buffer to read into.
1617 size - Size in bytes to read.
1625 [HostProtection(ExternalThreading=true)]
1626 public override IAsyncResult BeginRead(byte[] buffer, int offset, int size, AsyncCallback callback, object state) {
1628 using (GlobalLog.SetThreadKind(ThreadKinds.User | ThreadKinds.Async)) {
1630 GlobalLog.Print("ConnectStream#" + ValidationHelper.HashString(this) + "::BeginRead() " + ValidationHelper.HashString(m_Connection) + ", " + offset.ToString() + ", " + size.ToString());
1631 if(Logging.On)Logging.Enter(Logging.Web, this, "BeginRead", "");
1634 // parameter validation
1637 throw new NotSupportedException(SR.GetString(SR.net_writeonlystream));
1640 throw new ArgumentNullException("buffer");
1642 if (offset<0 || offset>buffer.Length) {
1643 throw new ArgumentOutOfRangeException("offset");
1645 if (size<0 || size>buffer.Length-offset) {
1646 throw new ArgumentOutOfRangeException("size");
1650 // if we have a stream error, or we've already shut down this socket
1651 // then we must prevent new BeginRead/BeginWrite's from getting
1652 // submited to the socket, since we've already closed the stream.
1655 if (ErrorInStream) {
1656 throw m_ErrorException;
1660 throw new WebException(
1661 NetRes.GetWebStatusString("net_requestaborted", WebExceptionStatus.ConnectionClosed),
1662 WebExceptionStatus.ConnectionClosed);
1665 if (m_Request.Aborted) {
1666 GlobalLog.Print("ConnectStream#" + ValidationHelper.HashString(this) + "::InternalWrite() throwing");
1667 throw new WebException(
1668 NetRes.GetWebStatusString("net_requestaborted", WebExceptionStatus.RequestCanceled),
1669 WebExceptionStatus.RequestCanceled);
1674 // if we fail/hang this call for some reason,
1675 // this Nesting count we be non-0, so that when we
1676 // close this stream, we will abort the socket.
1679 int nesting = Interlocked.CompareExchange(ref m_CallNesting, Nesting.IoInProgress, Nesting.Idle);
1680 GlobalLog.Print("BeginRead() In: callNesting : " + m_CallNesting.ToString());
1684 throw new NotSupportedException(SR.GetString(SR.net_no_concurrent_io_allowed));
1687 IAsyncResult result =
1688 BeginReadWithoutValidation(
1695 if(Logging.On)Logging.Exit(Logging.Web, this, "BeginRead", result);
1704 BeginReadWithoutValidation - Read from the connection.
1706 internal version of BeginRead above, without validation
1708 This method reads from the network, or our internal buffer if there's
1709 data in that. If there's not, we'll read from the network. If we're
1710 doing chunked decoding, we'll decode it before returning from this
1716 buffer - Buffer to read into.
1717 offset - Offset in buffer to read into.
1718 size - Size in bytes to read.
1725 private IAsyncResult BeginReadWithoutValidation(byte[] buffer, int offset, int size, AsyncCallback callback, object state) {
1726 GlobalLog.Enter("ConnectStream#" + ValidationHelper.HashString(this) + "::BeginReadWithoutValidation", ValidationHelper.HashString(m_Connection) + ", " + offset.ToString() + ", " + size.ToString());
1727 GlobalLog.ThreadContract(ThreadKinds.Unknown, "ConnectStream#" + ValidationHelper.HashString(this) + "::BeginReadWithoutValidation");
1730 // Figure out how much we should really read.
1733 int bytesToRead = 0;
1736 if (!m_ChunkEofRecvd) {
1737 return m_ChunkParser.ReadAsync(this, buffer, offset, size, callback, state);
1743 // Not doing chunked, so don't read more than is left.
1746 if (m_ReadBytes != -1) {
1747 bytesToRead = (int)Math.Min(m_ReadBytes, (long)size);
1754 // If we're not going to read anything, either because they're
1755 // not asking for anything or there's nothing left, bail
1758 if (bytesToRead == 0 || this.Eof) {
1759 NestedSingleAsyncResult completedResult = new NestedSingleAsyncResult(this, state, callback, ZeroLengthRead);
1760 GlobalLog.Leave("ConnectStream#" + ValidationHelper.HashString(this) + "::BeginReadWithoutValidation() completed, bytesToRead: " + bytesToRead + " Eof: " + this.Eof.ToString());
1761 return completedResult;
1766 int bytesAlreadyRead = 0;
1767 if (m_ReadBufferSize > 0)
1769 bytesAlreadyRead = FillFromBufferedData(buffer, ref offset, ref bytesToRead);
1770 if (bytesToRead == 0)
1772 NestedSingleAsyncResult completedResult = new NestedSingleAsyncResult(this, state, callback, bytesAlreadyRead);
1773 GlobalLog.Leave("ConnectStream::BeginReadWithoutValidation");
1774 return completedResult;
1780 GlobalLog.LeaveException("ConnectStream::BeginReadWithoutValidation", m_ErrorException);
1781 throw m_ErrorException;
1784 GlobalLog.Assert(m_DoneCalled == 0 || m_ReadBytes != -1, "BeginRead: Calling BeginRead after ReadDone.|m_DoneCalled > 0 && m_ReadBytes == -1");
1786 // Keep track of this during the read so it can be added back at the end.
1787 m_BytesAlreadyTransferred = bytesAlreadyRead;
1789 IAsyncResult asyncResult = m_Connection.BeginRead(buffer, offset, bytesToRead, callback, state);
1791 // a null return indicates that the connection was closed underneath us.
1792 if (asyncResult == null)
1794 m_BytesAlreadyTransferred = 0;
1795 m_ErrorException = new WebException(
1796 NetRes.GetWebStatusString("net_requestaborted", WebExceptionStatus.RequestCanceled),
1797 WebExceptionStatus.RequestCanceled);
1799 GlobalLog.LeaveException("ConnectStream::BeginReadWithoutValidation", m_ErrorException);
1800 throw m_ErrorException;
1803 GlobalLog.Leave("ConnectStream#" + ValidationHelper.HashString(this) + "::BeginReadWithoutValidation() called BeginRead");
1806 catch (Exception exception) {
1808 GlobalLog.LeaveException("ConnectStream#" + ValidationHelper.HashString(this) + "::BeginReadWithoutValidation", exception);
1817 This is an interal version of Read without validation,
1818 that is called from the Chunked code as well the normal codepaths.
1822 private int InternalRead(byte[] buffer, int offset, int size) {
1823 GlobalLog.ThreadContract(ThreadKinds.Sync, "ConnectStream#" + ValidationHelper.HashString(this) + "::InternalRead");
1825 // Read anything first out of the buffer
1826 int bytesToRead = FillFromBufferedData(buffer, ref offset, ref size);
1827 if (bytesToRead>0) {
1831 // otherwise, we need to read more data from the connection.
1832 if (ErrorInStream) {
1833 GlobalLog.LeaveException("ConnectStream::InternalBeginRead", m_ErrorException);
1834 throw m_ErrorException;
1837 bytesToRead = m_Connection.Read(
1849 This callback is only used by chunking as the last step of its multi-phase async operation.
1853 asyncResult - IAsyncResult generated from BeginWrite
1860 private void ReadCallback(IAsyncResult asyncResult) {
1861 GlobalLog.Enter("ConnectStream::ReadCallback", "asyncResult=#"+ValidationHelper.HashString(asyncResult));
1862 GlobalLog.ThreadContract(ThreadKinds.Unknown, "ConnectStream::ReadCallback");
1865 // we called m_Connection.BeginRead() previously that call
1866 // completed and called our internal callback
1867 // we passed the NestedSingleAsyncResult (that we then returned to the user)
1868 // as the state of this call, so build it back:
1870 NestedSingleAsyncResult castedAsyncResult = (NestedSingleAsyncResult)asyncResult.AsyncState;
1871 ConnectStream thisConnectStream = (ConnectStream)castedAsyncResult.AsyncObject;
1873 object result = null;
1876 int bytesTransferred = thisConnectStream.m_Connection.EndRead(asyncResult);
1877 if(Logging.On)Logging.Dump(Logging.Web, thisConnectStream, "ReadCallback", castedAsyncResult.Buffer, castedAsyncResult.Offset, Math.Min(castedAsyncResult.Size, bytesTransferred));
1879 result = bytesTransferred;
1881 catch (Exception exception) {
1885 castedAsyncResult.InvokeCallback(result);
1886 GlobalLog.Leave("ConnectStream::ReadCallback");
1891 EndRead - Finishes off the Read for the Connection
1892 EndReadWithoutValidation
1894 This method completes the async call created from BeginRead,
1895 it attempts to determine how many bytes were actually read,
1896 and if any errors occured.
1899 asyncResult - created by BeginRead
1902 int - size of bytes read, or < 0 on error
1906 public override int EndRead(IAsyncResult asyncResult) {
1908 using (GlobalLog.SetThreadKind(ThreadKinds.User)) {
1910 if (Logging.On) Logging.Enter(Logging.Web, this, "EndRead", "");
1913 // parameter validation
1915 if (asyncResult==null) {
1916 throw new ArgumentNullException("asyncResult");
1919 int bytesTransferred;
1920 bool zeroLengthRead = false;
1921 if ((asyncResult.GetType() == typeof(NestedSingleAsyncResult)) || m_Chunked)
1923 LazyAsyncResult castedAsyncResult = (LazyAsyncResult)asyncResult;
1924 if (castedAsyncResult.AsyncObject != this)
1926 throw new ArgumentException(SR.GetString(SR.net_io_invalidasyncresult), "asyncResult");
1928 if (castedAsyncResult.EndCalled)
1930 throw new InvalidOperationException(SR.GetString(SR.net_io_invalidendcall, "EndRead"));
1932 castedAsyncResult.EndCalled = true;
1936 GlobalLog.LeaveException("ConnectStream::EndRead", m_ErrorException);
1937 throw m_ErrorException;
1940 object result = castedAsyncResult.InternalWaitForCompletion();
1942 Exception errorException = result as Exception;
1943 if (errorException != null)
1945 IOError(errorException, false);
1946 bytesTransferred = -1;
1950 // If it's a NestedSingleAsyncResult, we completed it ourselves with our own result.
1953 bytesTransferred = 0;
1955 else if (result == ZeroLengthRead)
1957 bytesTransferred = 0;
1958 zeroLengthRead = true;
1964 bytesTransferred = (int) result;
1966 if (m_Chunked && (bytesTransferred == 0))
1968 m_ChunkEofRecvd = true;
1972 catch (InvalidCastException)
1974 bytesTransferred = -1;
1981 // If it's not a NestedSingleAsyncResult, we forwarded directly to the Connection and need to call EndRead.
1984 bytesTransferred = m_Connection.EndRead(asyncResult);
1986 catch (Exception exception)
1988 if (NclUtilities.IsFatal(exception)) throw;
1990 IOError(exception, false);
1991 bytesTransferred = -1;
1995 bytesTransferred = EndReadWithoutValidation(bytesTransferred, zeroLengthRead);
1997 Interlocked.CompareExchange(ref m_CallNesting, Nesting.Idle, Nesting.IoInProgress);
1998 GlobalLog.Print("EndRead() callNesting: " + m_CallNesting.ToString());
2000 if(Logging.On)Logging.Exit(Logging.Web, this, "EndRead", bytesTransferred);
2001 if (m_ErrorException != null) {
2002 throw (m_ErrorException);
2005 return bytesTransferred;
2013 EndReadWithoutValidation - Finishes off the Read for the Connection
2014 Called internally by EndRead.
2016 This method completes the async call created from BeginRead,
2017 it attempts to determine how many bytes were actually read,
2018 and if any errors occured.
2021 asyncResult - created by BeginRead
2024 int - size of bytes read, or < 0 on error
2027 private int EndReadWithoutValidation(int bytesTransferred, bool zeroLengthRead)
2029 GlobalLog.Enter("ConnectStream#" + ValidationHelper.HashString(this) + "::EndReadWithoutValidation", bytesTransferred.ToString());
2031 int bytesAlreadyTransferred = m_BytesAlreadyTransferred;
2032 m_BytesAlreadyTransferred = 0;
2037 // we're not chunking, a note about error
2038 // checking here, in some cases due to 1.0
2039 // servers we need to read until 0 bytes,
2040 // or a server reset, therefore, we may need
2041 // ignore sockets errors
2044 bool doneReading = false;
2046 // if its finished without async, just use what was read already from the buffer,
2047 // otherwise we call the Connection's EndRead to find out
2048 if (bytesTransferred <= 0)
2051 // We read 0 bytes from the connection, or it had an error. This is OK if we're
2052 // reading to end, it's an error otherwise.
2054 if (m_ReadBytes != -1 && (bytesTransferred < 0 || !zeroLengthRead))
2056 IOError(null, false);
2060 // We're reading to end, and we found the end, by reading 0 bytes
2063 bytesTransferred = 0;
2067 bytesTransferred += bytesAlreadyTransferred;
2070 // Not chunking. Update our read bytes state and return what we've read.
2072 if (m_ReadBytes != -1) {
2073 m_ReadBytes -= bytesTransferred;
2075 GlobalLog.Assert(m_ReadBytes >= 0, "ConnectStream: Attempting to read more bytes than available.|m_ReadBytes < 0");
2077 GlobalLog.Print("m_ReadBytes = "+m_ReadBytes);
2080 if (m_ReadBytes == 0 || doneReading) {
2081 // We're all done reading, tell the connection that.
2085 // indicate to cache that read completed OK
2092 GlobalLog.Leave("ConnectStream#" + ValidationHelper.HashString(this) + "::EndRead", bytesTransferred);
2093 return bytesTransferred;
2097 private static void WriteHeadersCallback(IAsyncResult ar)
2099 if(ar.CompletedSynchronously){
2103 WriteHeadersCallbackState state = (WriteHeadersCallbackState)ar.AsyncState;
2104 ConnectStream stream = state.stream;
2105 HttpWebRequest request = state.request;
2106 WebExceptionStatus error = WebExceptionStatus.SendFailure;
2108 //m_Request.writebuffer may be set to null on resubmit before method exits
2110 GlobalLog.Enter("ConnectStream#" + ValidationHelper.HashString(stream) + "::WriteHeadersCallback", "Connection#" + ValidationHelper.HashString(stream.m_Connection) + ", " + request.WriteBufferLength.ToString());
2112 stream.m_Connection.EndWrite(ar);
2113 if (stream.m_Connection.m_InnerException != null)
2114 throw stream.m_Connection.m_InnerException;
2116 error = WebExceptionStatus.Success;
2118 catch (Exception e){
2119 stream.HandleWriteHeadersException(e, error);
2122 stream.ExchangeCallNesting(Nesting.Idle, Nesting.InternalIO);
2124 if (error == WebExceptionStatus.Success && !stream.ErrorInStream) {
2126 error = WebExceptionStatus.ReceiveFailure;
2128 // Start checking async for responses. This needs to happen outside of the Nesting.InternalIO lock
2129 // because it may receive, process, and start a resubmit.
2131 request.StartAsync100ContinueTimer();
2132 stream.m_Connection.CheckStartReceive(request);
2134 if (stream.m_Connection.m_InnerException != null)
2135 throw stream.m_Connection.m_InnerException;
2137 error = WebExceptionStatus.Success;
2139 catch (Exception e) {
2140 stream.HandleWriteHeadersException(e, error);
2144 // Resend data, etc.
2145 request.WriteHeadersCallback(error, stream, true);
2146 request.FreeWriteBuffer();
2147 GlobalLog.Leave("ConnectStream#" + ValidationHelper.HashString(stream) + "::WriteHeadersCallback",request.WriteBufferLength.ToString());
2154 This function writes header data to the network. Headers are special
2155 in that they don't have any non-header transforms applied to them,
2156 and are not subject to content-length constraints. We just write them
2157 through, and if we're done writing headers we tell the connection that.
2160 WebExceptionStatus.Pending - we don't have a stream yet.
2161 WebExceptionStatus.SendFailure - there was an error while writing to the wire.
2162 WebExceptionStatus.Success - success.
2165 internal void WriteHeaders(bool async) {
2166 GlobalLog.Enter("ConnectStream#" + ValidationHelper.HashString(this) + "::WriteHeaders", "Connection#" + ValidationHelper.HashString(m_Connection) + ", headers buffer size = " + m_Request.WriteBufferLength.ToString());
2168 WebExceptionStatus error = WebExceptionStatus.SendFailure;
2172 //m_Request.WriteBuffer may be set to null on resubmit before method exits
2173 byte[] writeBuffer = m_Request.WriteBuffer;
2174 int writeBufferLength = m_Request.WriteBufferLength;
2178 Interlocked.CompareExchange(ref m_CallNesting, Nesting.InternalIO, Nesting.Idle);
2179 GlobalLog.Print("WriteHeaders() callNesting: " + m_CallNesting.ToString());
2181 if (Logging.On) Logging.PrintInfo(Logging.Web, this, SR.GetString(SR.net_log_sending_headers, m_Request.Headers.ToString(true)));
2185 WriteHeadersCallbackState state = new WriteHeadersCallbackState(m_Request, this);
2186 IAsyncResult ar = m_Connection.UnsafeBeginWrite(writeBuffer,0,writeBufferLength, m_WriteHeadersCallback, state);
2187 if (ar.CompletedSynchronously) {
2188 m_Connection.EndWrite(ar);
2189 error = WebExceptionStatus.Success;
2192 error = WebExceptionStatus.Pending;
2194 _PendingResult = ar;
2200 SafeSetSocketTimeout(SocketShutdown.Send);
2201 m_Connection.Write(writeBuffer, 0, writeBufferLength);
2202 error = WebExceptionStatus.Success;
2205 catch (Exception e) {
2206 HandleWriteHeadersException(e, error);
2209 if(error != WebExceptionStatus.Pending) {
2210 Interlocked.CompareExchange(ref m_CallNesting, Nesting.Idle, Nesting.InternalIO);
2211 GlobalLog.Print("WriteHeaders() callNesting: " + m_CallNesting.ToString());
2217 GlobalLog.Print("ConnectStream#" + ValidationHelper.HashString(this) + "::WriteHeaders() ignoring since ErrorInStream = true");
2220 if (error == WebExceptionStatus.Pending)
2222 return; // WriteHeadersCallback will finish this async
2225 if (error == WebExceptionStatus.Success && !ErrorInStream)
2227 error = WebExceptionStatus.ReceiveFailure;
2229 // Start checking for responses. This needs to happen outside of the Nesting.InternalIO lock
2230 // because it may receive, process, and start a resubmit.
2235 m_Request.StartAsync100ContinueTimer();
2236 m_Connection.CheckStartReceive(m_Request);
2240 m_Request.StartContinueWait();
2241 m_Connection.CheckStartReceive(m_Request);
2242 if (m_Request.ShouldWaitFor100Continue()) // Sync poll
2244 PollAndRead(m_Request.UserRetrievedWriteStream);
2247 error = WebExceptionStatus.Success;
2251 HandleWriteHeadersException(e, error);
2255 m_Request.WriteHeadersCallback(error, this, async);
2256 m_Request.FreeWriteBuffer();
2259 private void HandleWriteHeadersException(Exception e, WebExceptionStatus error) {
2260 GlobalLog.Print("ConnectStream#" + ValidationHelper.HashString(this) + "::WriteHeaders Exception: " + e.ToString());
2262 if (e is IOException || e is ObjectDisposedException)
2264 //new connection but reset from server on inital send
2265 if (!m_Connection.AtLeastOneResponseReceived && !m_Request.BodyStarted) {
2266 e = new WebException(
2267 NetRes.GetWebStatusString("net_connclosed", error),
2269 WebExceptionInternalStatus.Recoverable,
2273 e = new WebException(
2274 NetRes.GetWebStatusString("net_connclosed", error),
2276 m_Connection.AtLeastOneResponseReceived ? WebExceptionInternalStatus.Isolated : WebExceptionInternalStatus.RequestFatal,
2284 internal ChannelBinding GetChannelBinding(ChannelBindingKind kind)
2286 GlobalLog.Enter("ConnectStream#" + ValidationHelper.HashString(this) + "::GetChannelBinding", kind.ToString());
2288 ChannelBinding binding = null;
2289 TlsStream tlsStream = m_Connection.NetworkStream as TlsStream;
2291 if (tlsStream != null)
2293 binding = tlsStream.GetChannelBinding(kind);
2296 GlobalLog.Leave("ConnectStream#" + ValidationHelper.HashString(this) + "::GetChannelBinding", ValidationHelper.HashString(binding));
2300 // Wrapper for Connection
2301 internal void PollAndRead(bool userRetrievedStream) {
2302 m_Connection.PollAndRead(m_Request, userRetrievedStream);
2305 private void SafeSetSocketTimeout(SocketShutdown mode) {
2312 if (mode == SocketShutdown.Receive) {
2313 timeout = ReadTimeout;
2314 } else /*if (mode == SocketShutdown.Send)*/ {
2315 timeout = WriteTimeout;
2317 Connection connection = m_Connection;
2318 if (connection!=null) {
2319 NetworkStream networkStream = connection.NetworkStream;
2320 if (networkStream!=null) {
2321 networkStream.SetSocketTimeoutOption(mode, timeout, false);
2326 internal int SetRtcOption(byte[] rtcInputSocketConfig, byte[] rtcOutputSocketResult)
2328 Socket socket = InternalSocket;
2329 Debug.Assert(socket != null, "No Socket");
2332 socket.IOControl(ApplyTransportSetting, rtcInputSocketConfig, null);
2333 socket.IOControl(QueryTransportSetting, rtcInputSocketConfig, rtcOutputSocketResult);
2335 catch (SocketException ex)
2338 // Report error to QuerySetting
2339 return ex.ErrorCode;
2344 private Socket InternalSocket {
2346 Connection connection = m_Connection;
2347 if (connection != null)
2349 NetworkStream networkStream = connection.NetworkStream;
2350 if (networkStream != null)
2352 return networkStream.InternalSocket;
2360 Close - Close the stream
2362 Called when the stream is closed. We close our parent stream first.
2363 Then if this is a write stream, we'll send the terminating chunk
2364 (if needed) and call the connection DoneWriting() method.
2376 protected override void Dispose(bool disposing) {
2378 using (GlobalLog.SetThreadKind(ThreadKinds.User | ThreadKinds.Sync)) {
2383 GlobalLog.Print("ConnectStream#" + ValidationHelper.HashString(this) + "::Close()");
2384 if(Logging.On)Logging.Enter(Logging.Web, this, "Close", "");
2385 ((ICloseEx)this).CloseEx(CloseExState.Normal);
2386 if(Logging.On)Logging.Exit(Logging.Web, this, "Close", "");
2390 base.Dispose(disposing);
2397 internal void CloseInternal(bool internalCall) {
2398 GlobalLog.ThreadContract(ThreadKinds.Unknown, "ConnectStream#" + ValidationHelper.HashString(this) + "::Abort");
2399 ((ICloseEx)this).CloseEx((internalCall ? CloseExState.Silent : CloseExState.Normal));
2402 void ICloseEx.CloseEx(CloseExState closeState) {
2403 GlobalLog.ThreadContract(ThreadKinds.Unknown, "ConnectStream#" + ValidationHelper.HashString(this) + "::Abort");
2405 (closeState & CloseExState.Silent) != 0,
2406 (closeState & CloseExState.Abort) != 0
2408 GC.SuppressFinalize(this);
2412 // Optionally sends chunk terminator and proceeds with close that was collided with pending user write IO
2414 void ResumeInternalClose(LazyAsyncResult userResult)
2416 GlobalLog.Print("ConnectStream##" + ValidationHelper.HashString(this) + "::ResumeInternalClose(), userResult:" + userResult);
2418 // write stream. terminate our chunking if needed.
2420 if (WriteChunked && !ErrorInStream && !m_IgnoreSocketErrors)
2422 m_IgnoreSocketErrors = true;
2424 if (userResult == null)
2426 SafeSetSocketTimeout(SocketShutdown.Send);
2427 m_Connection.Write(NclConstants.ChunkTerminator, 0, NclConstants.ChunkTerminator.Length);
2431 m_Connection.BeginWrite(NclConstants.ChunkTerminator, 0, NclConstants.ChunkTerminator.Length, new AsyncCallback(ResumeClose_Part2_Wrapper), userResult);
2435 catch (Exception exception) {
2436 GlobalLog.Print("ConnectStream#" + ValidationHelper.HashString(this) + "::CloseInternal() exceptionOnWrite:" + exception.Message);
2439 ResumeClose_Part2(userResult); //never throws
2442 void ResumeClose_Part2_Wrapper(IAsyncResult ar)
2445 m_Connection.EndWrite(ar);
2447 catch (Exception exception) {
2448 GlobalLog.Print("ConnectStream#" + ValidationHelper.HashString(this) + "::ResumeClose_Part2_Wrapper() ignoring exceptionOnWrite:" + exception.Message);
2450 ResumeClose_Part2((LazyAsyncResult)ar.AsyncState);
2453 private void ResumeClose_Part2(LazyAsyncResult userResult)
2461 GlobalLog.Print("ConnectStream#" + ValidationHelper.HashString(this) + "::ResumeClose_Part2() Aborting the connection");
2462 m_Connection.AbortSocket(true);
2468 GlobalLog.Leave("ConnectStream#" + ValidationHelper.HashString(this) + "::ResumeClose_Part2", "Done");
2474 if (userResult != null)
2476 userResult.InvokeCallback();
2481 // The number should be reasonalbly large
2482 private const int AlreadyAborted = 777777;
2484 private void CloseInternal(bool internalCall, bool aborting) {
2485 GlobalLog.Enter("ConnectStream#" + ValidationHelper.HashString(this) + "::CloseInternal", internalCall.ToString());
2486 GlobalLog.ThreadContract(ThreadKinds.Unknown, "ConnectStream#" + ValidationHelper.HashString(this) + "::Abort");
2488 bool normalShutDown = !aborting;
2489 Exception exceptionOnWrite = null;
2492 // We have to prevent recursion, because we'll call our parents, close,
2493 // which might try to flush data. If we're in an error situation, that
2494 // will cause an error on the write, which will cause Close to be called
2497 GlobalLog.Print("ConnectStream#" + ValidationHelper.HashString(this) + "::CloseInternal() m_ShutDown:" + m_ShutDown.ToString() + " m_CallNesting:" + m_CallNesting.ToString() + " m_DoneCalled:" + m_DoneCalled.ToString());
2499 //If this is an abort (aborting == true) of a write stream then we will call request.Abort()
2500 //that will call us again. To prevent a recursion here, only one abort is allowed.
2501 //However, Abort must still override previous normal close if any.
2503 if (Interlocked.Exchange(ref m_ShutDown, AlreadyAborted) >= AlreadyAborted) {
2504 GlobalLog.Leave("ConnectStream#" + ValidationHelper.HashString(this) + "::CloseInternal", "already has been Aborted");
2509 //If m_ShutDown != 0, then this method has been already called before,
2510 //Hence disregard this (presumably normal) extra close
2511 if (Interlocked.Increment(ref m_ShutDown) > 1) {
2512 GlobalLog.Leave("ConnectStream#" + ValidationHelper.HashString(this) + "::CloseInternal", "already has been closed");
2516 RequestLifetimeSetter.Report(m_RequestLifetimeSetter);
2520 // Since this should be the last call made, we should be at 0
2521 // If not on the read side then it's an error so we should close the socket
2522 // If not on the write side then MAY BE we want this write stream to ignore all
2523 // further writes and optionally send chunk terminator.
2525 int nesting = (IsPostStream && internalCall && !IgnoreSocketErrors && !BufferOnly && normalShutDown && !NclUtilities.HasShutdownStarted)? Nesting.Closed: Nesting.InError;
2526 if (Interlocked.Exchange(ref m_CallNesting, nesting) == Nesting.IoInProgress)
2528 if (nesting == Nesting.Closed)
2530 GlobalLog.Print("ConnectStream#" + ValidationHelper.HashString(this) + "::CloseInternal() PostStream, Internal call and m_CallNesting==1, defer closing until user write completes");
2533 normalShutDown &= !NclUtilities.HasShutdownStarted;
2535 GlobalLog.Print("Close m_CallNesting: " + m_CallNesting.ToString());
2537 // Questionable: Thsi is to avoid throwing on public Close() when IgnoreSocketErrors==true
2538 if (IgnoreSocketErrors && IsPostStream && !internalCall)
2540 m_BytesLeftToWrite = 0;
2543 GlobalLog.Print("ConnectStream#" + ValidationHelper.HashString(this) + "::CloseInternal() normalShutDown:" + normalShutDown.ToString() + " m_CallNesting:" + m_CallNesting.ToString() + " m_DoneCalled:" + m_DoneCalled.ToString());
2546 if (IgnoreSocketErrors || !normalShutDown) {
2547 GlobalLog.Print("ConnectStream#" + ValidationHelper.HashString(this) + "::CloseInternal() don't read/write on this, dead connection stream.");
2549 else if (!WriteStream) {
2553 GlobalLog.Print("ConnectStream#" + ValidationHelper.HashString(this) + "::CloseInternal() callNesting: " + m_CallNesting.ToString());
2554 GlobalLog.Print("ConnectStream#" + ValidationHelper.HashString(this) + "::CloseInternal() read stream, calling DrainSocket()");
2556 using (GlobalLog.SetThreadKind(ThreadKinds.Sync)) {
2559 // A race condition still exists when a different thread calls HttpWebRequest.Abort().
2560 // This is expected and handled within DrainSocket().
2562 Connection connection = m_Connection;
2563 if (connection != null)
2565 NetworkStream networkStream = connection.NetworkStream;
2566 if (networkStream != null && networkStream.Connected)
2568 normalShutDown = DrainSocket();
2577 // write stream. terminate our chunking if needed.
2580 if (!ErrorInStream) {
2582 // if not error already, then...
2583 // first handle chunking case
2587 // no need to buffer here:
2588 // on resubmit, we won't be chunking anyway this will send 5 bytes on the wire
2590 GlobalLog.Print("ConnectStream#" + ValidationHelper.HashString(this) + "::CloseInternal() Chunked, writing ChunkTerminator");
2592 // The idea behind is that closed stream must not write anything to the wire
2593 // Still if we are chunking, the now buffering and future resubmit is possible
2594 if (!m_IgnoreSocketErrors) {
2595 m_IgnoreSocketErrors = true;
2596 SafeSetSocketTimeout(SocketShutdown.Send);
2599 // Until there is an async version of this, we have to assert Sync privileges here.
2600 using (GlobalLog.SetThreadKind(ThreadKinds.Sync)) {
2602 m_Connection.Write(NclConstants.ChunkTerminator, 0, NclConstants.ChunkTerminator.Length);
2609 GlobalLog.Print("ConnectStream#" + ValidationHelper.HashString(this) + "::CloseInternal() IGNORE chunk write fault");
2611 m_BytesLeftToWrite = 0;
2613 else if (BytesLeftToWrite>0) {
2614 if (internalCall) { // ContentLength, after a 401 without buffering we need to close the connection
2615 m_Connection.AbortSocket(true);
2617 else { // The user called close before they wrote ContentLength number of bytes to the stream
2619 // not enough bytes written to client
2621 GlobalLog.Leave("ConnectStream#" + ValidationHelper.HashString(this) + "::CloseInternal() BytesLeftToWrite:" + BytesLeftToWrite.ToString() + " throwing not enough bytes written");
2622 throw new IOException(SR.GetString(SR.net_io_notenoughbyteswritten));
2625 else if (BufferOnly) {
2627 // now we need to use the saved reference to the request the client
2628 // closed the write stream. we need to wake up the request, so that it
2629 // sends the headers and kick off resubmitting of buffered entity body
2631 GlobalLog.Assert(m_Request != null, "ConnectStream#{0}::CloseInternal|m_Request == null", ValidationHelper.HashString(this));
2632 m_BytesLeftToWrite = BufferedData.Length;
2633 m_Request.SwitchToContentLength();
2635 // writing the headers will kick off the whole request submission process
2636 // (including waiting for the 100 Continue and writing the whole entity body)
2638 SafeSetSocketTimeout(SocketShutdown.Send);
2639 m_Request.NeedEndSubmitRequest();
2640 GlobalLog.Leave("ConnectStream#" + ValidationHelper.HashString(this) + "::CloseInternal", "Done");
2645 normalShutDown = false;
2648 catch (Exception exception) {
2649 normalShutDown = false;
2651 if (NclUtilities.IsFatal(exception))
2653 m_ErrorException = exception;
2657 exceptionOnWrite = new WebException(
2658 NetRes.GetWebStatusString("net_requestaborted", WebExceptionStatus.RequestCanceled),
2660 WebExceptionStatus.RequestCanceled,
2663 GlobalLog.Print("ConnectStream#" + ValidationHelper.HashString(this) + "::CloseInternal() exceptionOnWrite:" + exceptionOnWrite.Message);
2667 GlobalLog.Print("ConnectStream#" + ValidationHelper.HashString(this) + "::CloseInternal() normalShutDown:" + normalShutDown.ToString() + " m_CallNesting:" + m_CallNesting.ToString() + " m_DoneCalled:" + m_DoneCalled.ToString());
2669 if (!normalShutDown && m_DoneCalled==0) {
2670 // If a normal Close (aborting == false) has turned into Abort _inside_ this method,
2671 // then check if another abort has been charged from other thread
2672 if (!aborting && Interlocked.Exchange(ref m_ShutDown, AlreadyAborted) >= AlreadyAborted){
2673 GlobalLog.Leave("ConnectStream#" + ValidationHelper.HashString(this) + "::CloseInternal", "other thread has charged Abort(), canceling that one");
2677 // then abort the connection if we finished in error
2678 // note: if m_DoneCalled != 0, then we no longer have
2679 // control of the socket, so closing would cause us
2680 // to close someone else's socket/connection.
2684 NetRes.GetWebStatusString("net_requestaborted", WebExceptionStatus.RequestCanceled),
2685 WebExceptionStatus.RequestCanceled);
2687 GlobalLog.Print("ConnectStream#" + ValidationHelper.HashString(this) + "::CloseInternal() Aborting the connection");
2689 m_Connection.AbortSocket(true);
2690 // For write stream Abort() we depend on either of two, i.e:
2691 // 1. The connection BeginRead is curently posted (means there are no response headers received yet)
2692 // 2. The response (read) stream must be closed as well if aborted this (write) stream.
2693 // Next block takes care of (2) since otherwise, (1) is true.
2695 HttpWebRequest req = m_Request;
2701 if (exceptionOnWrite != null) {
2702 GlobalLog.Print("ConnectStream#" + ValidationHelper.HashString(this) + "::CloseInternal() calling CallDone() on exceptionOnWrite:" + exceptionOnWrite.Message);
2706 if (!internalCall) {
2707 GlobalLog.LeaveException("ConnectStream#" + ValidationHelper.HashString(this) + "::CloseInternal() throwing:", exceptionOnWrite);
2708 throw exceptionOnWrite;
2713 // Let the connection know we're done writing or reading.
2715 GlobalLog.Print("ConnectStream#" + ValidationHelper.HashString(this) + "::CloseInternal() calling CallDone()");
2719 GlobalLog.Leave("ConnectStream#" + ValidationHelper.HashString(this) + "::CloseInternal", "Done");
2724 Flush - Flush the stream
2726 Called when the user wants to flush the stream. This is meaningless to
2727 us, so we just ignore it.
2740 public override void Flush() {
2743 public override Task FlushAsync(CancellationToken cancellationToken)
2745 return Task.CompletedTask;
2749 Seek - Seek on the stream
2751 Called when the user wants to seek the stream. Since we don't support
2752 seek, we'll throw an exception.
2756 offset - offset to see
2757 origin - where to start seeking
2766 public override long Seek(long offset, SeekOrigin origin) {
2767 throw new NotSupportedException(SR.GetString(SR.net_noseek));
2771 SetLength - Set the length on the stream
2773 Called when the user wants to set the stream length. Since we don't
2774 support seek, we'll throw an exception.
2778 value - length of stream to set
2787 public override void SetLength(long value) {
2788 throw new NotSupportedException(SR.GetString(SR.net_noseek));
2792 DrainSocket - Reads data from the connection, till we'll ready
2793 to finish off the stream, or close the connection for good.
2796 returns - bool true on success, false on failure
2799 private bool DrainSocket() {
2800 GlobalLog.Enter("ConnectStream::DrainSocket");
2801 GlobalLog.ThreadContract(ThreadKinds.Unknown, "ConnectStream#" + ValidationHelper.HashString(this) + "::DrainSocket");
2803 if (IgnoreSocketErrors)
2805 GlobalLog.Leave("ConnectStream#" + ValidationHelper.HashString(this) + "::DrainSocket() IgnoreSocketErrors == true, stream is dead.", true);
2809 // Optimization for non-chunked responses: when data is already present in the buffer, skip parsing it.
2810 long ReadBytes = m_ReadBytes;
2814 GlobalLog.Print("ConnectStream#" + ValidationHelper.HashString(this) + "::DrainSocket() m_ReadBytes:" + m_ReadBytes.ToString() + " m_ReadBufferSize:" + m_ReadBufferSize.ToString());
2816 if (m_ReadBufferSize != 0) {
2818 // There's stuff in our read buffer.
2819 // Update our internal read buffer state with what we took.
2821 m_ReadOffset += m_ReadBufferSize;
2823 GlobalLog.Print("ConnectStream#" + ValidationHelper.HashString(this) + "::DrainSocket() m_ReadBytes:" + m_ReadBytes.ToString() + " m_ReadOffset:" + m_ReadOffset.ToString());
2825 if (m_ReadBytes != -1) {
2827 m_ReadBytes -= m_ReadBufferSize;
2829 GlobalLog.Print("m_ReadBytes = "+m_ReadBytes);
2831 // error handling, we shouldn't hang here if trying to drain, and there
2832 // is a mismatch with Content-Length and actual bytes.
2834 // Note: I've seen this often happen with some sites where they return 204
2835 // in violation of HTTP/1.1 with a Content-Length > 0
2837 if (m_ReadBytes < 0) {
2838 GlobalLog.Print("ConnectStream#" + ValidationHelper.HashString(this) + "::DrainSocket() m_ReadBytes:" + m_ReadBytes.ToString() + " incorrect Content-Length? setting m_ReadBytes to 0 and returning false.");
2840 GlobalLog.Leave("ConnectStream::DrainSocket", false);
2844 m_ReadBufferSize = 0;
2846 // If the read buffer size has gone to 0, null out our pointer
2847 // to it so maybe it'll be garbage-collected faster.
2848 m_ReadBuffer = null;
2851 // exit out of drain Socket when there is no connection-length,
2852 // it doesn't make sense to drain a possible empty socket,
2853 // when we're just going to close it.
2854 if (ReadBytes == -1) {
2855 GlobalLog.Leave("ConnectStream#" + ValidationHelper.HashString(this) + "::DrainSocket() ReadBytes==-1, returning true");
2861 // in error or Eof, we may be in a weird state
2862 // so we need return if we as if we don't have any more
2863 // space to read, note Eof is true when there is an error
2867 GlobalLog.Leave("ConnectStream#" + ValidationHelper.HashString(this) + "::DrainSocket() Eof, returning true");
2871 int drainTimeoutMilliseconds = GetResponseDrainTimeout();
2874 // If we're draining more than 64K, then we should
2875 // just close the socket, since it would be costly to
2879 if ((drainTimeoutMilliseconds == 0) || (m_ReadBytes > c_MaxDrainBytes)) {
2880 GlobalLog.Print("ConnectStream#" + ValidationHelper.HashString(this) + "::DrainSocket() m_ReadBytes:" + m_ReadBytes.ToString() + " Closing the Connection");
2881 m_Connection.AbortSocket(false);
2882 GlobalLog.Leave("ConnectStream::DrainSocket", true);
2887 int totalBytesRead = 0;
2888 Stopwatch sw = new Stopwatch();
2892 // Override the receive timeout interval to correspond to the drainTimeoutMiliseconds.
2893 NetworkStream networkStream = m_Connection.NetworkStream;
2894 networkStream.SetSocketTimeoutOption(SocketShutdown.Receive, drainTimeoutMilliseconds, false);
2898 if (sw.ElapsedMilliseconds >= drainTimeoutMilliseconds) {
2899 GlobalLog.Print("ConnectStream#" + ValidationHelper.HashString(this) + "::DrainSocket() drain timeout exceeded.");
2903 bytesRead = ReadWithoutValidation(s_DrainingBuffer, 0, s_DrainingBuffer.Length, false);
2904 totalBytesRead += bytesRead;
2905 GlobalLog.Print("ConnectStream#" + ValidationHelper.HashString(this) + "::DrainSocket() drained bytesRead:" + bytesRead.ToString() + " bytes");
2906 } while ((bytesRead > 0) && (totalBytesRead <= c_MaxDrainBytes));
2908 catch (IOException) {
2910 // If SO_RCVTIMEO is set and we aborted the recv, the socket is in an indeterminate state.
2911 // We need to close this socket.
2914 GlobalLog.Print("ConnectStream#" + ValidationHelper.HashString(this) + "::DrainSocket() drain timeout exceeded.");
2917 catch (ObjectDisposedException)
2920 // A race condition exists when a different thread calls HttpWebRequest.Abort().
2921 // In certain cases, the SocketHandle or NetworkStream object may have been already closed/disposed.
2924 GlobalLog.Print("ConnectStream#" + ValidationHelper.HashString(this) + "::DrainSocket() the socket was already closed.");
2927 catch (Exception exception) {
2928 if (NclUtilities.IsFatal(exception)) throw;
2930 GlobalLog.Print("exception" + exception.ToString());
2937 if (bytesRead != 0) {
2939 // bytesRead > 0 means that we still have data from a chunked transfer but we have exceeded c_MaxDrainBytes.
2940 // bytesRead = -1 indicates a failure or a time out (either SO_RCVTIMEO or total execution time exceeded).
2941 // For appCompat reasons we must call AbortSocket(false) and return true below.
2944 m_Connection.AbortSocket(false);
2948 // Drain succesful. Set the receive timeout interval back to the original value.
2949 SafeSetSocketTimeout(SocketShutdown.Receive);
2952 GlobalLog.Leave("ConnectStream::DrainSocket", true);
2956 private int GetResponseDrainTimeout() {
2958 if (responseDrainTimeoutMilliseconds == Timeout.Infinite) {
2960 // Check if the config setting for response drain timeout is set. If not, use the default value.
2961 // Thread safety: It's OK to enter this if-block from multiple threads: We'll just initialize
2962 // responseDrainTimeoutMilliseconds multiple times.
2964 string appSetting = ConfigurationManager.AppSettings[responseDrainTimeoutAppSetting];
2965 int timeoutMilliseconds;
2966 if (int.TryParse(appSetting, NumberStyles.None, CultureInfo.InvariantCulture,
2967 out timeoutMilliseconds)) {
2969 responseDrainTimeoutMilliseconds = timeoutMilliseconds;
2972 responseDrainTimeoutMilliseconds = defaultResponseDrainTimeoutMilliseconds;
2976 return responseDrainTimeoutMilliseconds;
2979 internal static byte[] s_DrainingBuffer = new byte[4096];
2982 IOError - Handle an IOError on the stream.
2986 exception - optional Exception that will be later thrown
2990 Nothing or may throw
2993 private void IOError(Exception exception) {
2994 IOError(exception, true);
2997 // If willThrow=true, it means that the caller intends to throw an
2998 // exception. If there is already an earlier exception, then IO error
2999 // will throw that one instead. Otherwise IOError() will let the caller
3000 // throw the exception (typically the one passed in)
3001 // If willThrow = false, the user does not want an exception to be
3002 // thrown. So IOError should not throw an exception.
3003 private void IOError(Exception exception, bool willThrow)
3005 GlobalLog.Enter("ConnectStream#" + ValidationHelper.HashString(this) + "::IOError", "Connection# " + ValidationHelper.HashString(m_Connection));
3006 GlobalLog.ThreadContract(ThreadKinds.Unknown, "ConnectStream#" + ValidationHelper.HashString(this) + "::IOError");
3010 if (m_ErrorException == null)
3012 if ( exception == null ) {
3013 if ( !WriteStream ) {
3014 Msg = SR.GetString(SR.net_io_readfailure, SR.GetString(SR.net_io_connectionclosed));
3017 Msg = SR.GetString(SR.net_io_writefailure, SR.GetString(SR.net_io_connectionclosed));
3020 Interlocked.CompareExchange<Exception>(ref m_ErrorException, new IOException(Msg), null);
3024 willThrow &= Interlocked.CompareExchange<Exception>(ref m_ErrorException, exception, null) != null;
3028 ConnectionReturnResult returnResult = null;
3031 m_Connection.HandleConnectStreamException(true, false, WebExceptionStatus.SendFailure, ref returnResult, m_ErrorException);
3033 m_Connection.HandleConnectStreamException(false, true, WebExceptionStatus.ReceiveFailure, ref returnResult, m_ErrorException);
3036 CallDone(returnResult);
3038 GlobalLog.Leave("ConnectStream#" + ValidationHelper.HashString(this) + "::IOError");
3042 throw m_ErrorException;
3051 A private utility routine to convert an integer to a chunk header,
3052 which is an ASCII hex number followed by a CRLF. The header is retuned
3057 size - Chunk size to be encoded
3058 offset - Out parameter where we store offset into buffer.
3062 A byte array with the header in int.
3066 internal static byte[] GetChunkHeader(int size, out int offset) {
3067 GlobalLog.Enter("ConnectStream::GetChunkHeader", "size:" + size.ToString());
3069 uint Mask = 0xf0000000;
3070 byte[] Header = new byte[10];
3075 // Loop through the size, looking at each nibble. If it's not 0
3076 // convert it to hex. Save the index of the first non-zero
3079 for (i = 0; i < 8; i++, size <<= 4) {
3081 // offset == -1 means that we haven't found a non-zero nibble
3082 // yet. If we haven't found one, and the current one is zero,
3083 // don't do anything.
3086 if ((size & Mask) == 0) {
3092 // Either we have a non-zero nibble or we're no longer skipping
3093 // leading zeros. Convert this nibble to ASCII and save it.
3095 uint Temp = (uint)size >> 28;
3098 Header[i] = (byte)(Temp + '0');
3101 Header[i] = (byte)((Temp - 10) + 'A');
3105 // If we haven't found a non-zero nibble yet, we've found one
3106 // now, so remember that.
3113 Header[8] = (byte)'\r';
3114 Header[9] = (byte)'\n';
3116 GlobalLog.Leave("ConnectStream::GetChunkHeader");
3120 void IRequestLifetimeTracker.TrackRequestLifetime(long requestStartTimestamp)
3122 GlobalLog.Assert(m_RequestLifetimeSetter == null, "TrackRequestLifetime called more than once.");
3123 m_RequestLifetimeSetter = new RequestLifetimeSetter(requestStartTimestamp);
3127 // Base Memory stream does not overide BeginXXX and that will cause base Stream
3128 // to do async delegates and that is not thread safe on async Stream.Close()
3130 // This class will always complete async requests synchronously
3132 internal sealed class SyncMemoryStream: MemoryStream, IRequestLifetimeTracker {
3133 private int m_ReadTimeout;
3134 private int m_WriteTimeout;
3135 private RequestLifetimeSetter m_RequestLifetimeSetter;
3136 private bool m_Disposed;
3138 internal SyncMemoryStream(byte[] bytes): base(bytes, false)
3140 m_ReadTimeout = m_WriteTimeout = System.Threading.Timeout.Infinite;
3143 internal SyncMemoryStream(int initialCapacity): base(initialCapacity)
3145 m_ReadTimeout = m_WriteTimeout = System.Threading.Timeout.Infinite;
3147 public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback callback, Object state)
3149 int result = Read(buffer, offset, count);
3150 return new LazyAsyncResult(null, state, callback, result);
3153 public override int EndRead(IAsyncResult asyncResult)
3155 LazyAsyncResult lazyResult = (LazyAsyncResult)asyncResult;
3156 return (int) lazyResult.InternalWaitForCompletion();
3158 public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback callback, Object state)
3160 Write(buffer, offset, count);
3161 return new LazyAsyncResult(null, state, callback, null);
3164 public override void EndWrite(IAsyncResult asyncResult)
3166 LazyAsyncResult lazyResult = (LazyAsyncResult)asyncResult;
3167 lazyResult.InternalWaitForCompletion();
3170 public override bool CanTimeout {
3175 public override int ReadTimeout {
3177 return m_ReadTimeout;
3180 m_ReadTimeout = value;
3183 public override int WriteTimeout {
3185 return m_WriteTimeout;
3188 m_WriteTimeout = value;
3192 public void TrackRequestLifetime(long requestStartTimestamp)
3194 GlobalLog.Assert(m_RequestLifetimeSetter == null, "TrackRequestLifetime called more than once.");
3195 m_RequestLifetimeSetter = new RequestLifetimeSetter(requestStartTimestamp);
3198 protected override void Dispose(bool disposing)
3209 RequestLifetimeSetter.Report(m_RequestLifetimeSetter);
3212 base.Dispose(disposing);