1 //------------------------------------------------------------------------------
2 // <copyright file="_Connection.cs" company="Microsoft">
3 // Copyright (c) Microsoft Corporation. All rights reserved.
5 //------------------------------------------------------------------------------
10 using System.Collections;
11 using System.Collections.Generic;
12 using System.Diagnostics;
13 using System.Net.Sockets;
14 using System.Threading;
15 using System.Security;
16 using System.Globalization;
17 using System.Net.Configuration;
18 using System.Diagnostics.CodeAnalysis;
21 internal enum ReadState {
23 StatusLine, // about to parse status line
24 Headers, // reading headers
29 internal enum DataParseStatus {
30 NeedMoreData = 0, // need more data
31 ContinueParsing, // continue parsing
33 Invalid, // bad data format
34 DataTooBig, // data exceeds the allowed size
37 internal enum WriteBufferState {
44 // The enum lietrals will be displayed to the user in the exception message
45 internal enum WebParseErrorSection {
52 // The enum literal will be used to look up an error string in the resource file
53 internal enum WebParseErrorCode {
60 UnexpectedServerResponse
63 // Only defined for DataParseStatus.Invalid
64 struct WebParseError {
65 public WebParseErrorSection Section;
66 public WebParseErrorCode Code;
70 struct TunnelStateObject {
71 internal TunnelStateObject(HttpWebRequest r, Connection c){
76 internal Connection Connection;
77 internal HttpWebRequest OriginalRequest;
81 // ConnectionReturnResult - used to spool requests that have been completed,
82 // and need to be notified.
85 internal class ConnectionReturnResult {
87 private static readonly WaitCallback s_InvokeConnectionCallback = new WaitCallback(InvokeConnectionCallback);
89 private struct RequestContext {
90 internal HttpWebRequest Request;
91 internal object CoreResponse;
93 internal RequestContext(HttpWebRequest request, object coreResponse)
96 CoreResponse = coreResponse;
100 private List<RequestContext> m_Context;
102 internal ConnectionReturnResult()
104 m_Context = new List<RequestContext>(5);
107 internal ConnectionReturnResult(int capacity)
109 m_Context = new List<RequestContext>(capacity);
112 internal bool IsNotEmpty {
114 return m_Context.Count != 0;
118 internal static void Add(ref ConnectionReturnResult returnResult, HttpWebRequest request, CoreResponseData coreResponseData)
120 if (coreResponseData == null)
121 throw new InternalException(); //This may cause duplicate requests if we let it through in retail
123 if (returnResult == null) {
124 returnResult = new ConnectionReturnResult();
128 //This may cause duplicate requests if we let it through in retail but it's may be expensive to catch here
129 for (int j = 0; j < returnResult.m_Context.Count; ++j)
130 if ((object)returnResult.m_Context[j].Request == (object) request)
131 throw new InternalException();
134 returnResult.m_Context.Add(new RequestContext(request, coreResponseData));
137 internal static void AddExceptionRange(ref ConnectionReturnResult returnResult, HttpWebRequest [] requests, Exception exception)
139 AddExceptionRange(ref returnResult, requests, exception, exception);
141 internal static void AddExceptionRange(ref ConnectionReturnResult returnResult, HttpWebRequest [] requests, Exception exception, Exception firstRequestException)
144 //This may cause duplicate requests if we let it through in retail
145 if (exception == null)
146 throw new InternalException();
148 if (returnResult == null) {
149 returnResult = new ConnectionReturnResult(requests.Length);
151 // "abortedRequestExeption" is assigned to the "abortedRequest" or to the very first request if the latest is null
152 // Everyone else will get "exception"
153 for (int i = 0; i < requests.Length; ++i)
156 //This may cause duplicate requests if we let it through in retail but it's may be expensive to catch here
157 for (int j = 0; j < returnResult.m_Context.Count; ++j)
158 if ((object)returnResult.m_Context[j].Request == (object) requests[i])
159 throw new InternalException();
163 returnResult.m_Context.Add(new RequestContext(requests[i], firstRequestException));
165 returnResult.m_Context.Add(new RequestContext(requests[i], exception));
169 internal static void SetResponses(ConnectionReturnResult returnResult) {
170 if (returnResult==null){
174 GlobalLog.Print("ConnectionReturnResult#" + ValidationHelper.HashString(returnResult) + "::SetResponses() count=" + returnResult.m_Context.Count.ToString());
175 for (int i = 0; i < returnResult.m_Context.Count; i++)
178 HttpWebRequest request = returnResult.m_Context[i].Request;
180 CoreResponseData coreResponseData = returnResult.m_Context[i].CoreResponse as CoreResponseData;
181 if (coreResponseData == null)
182 GlobalLog.DebugRemoveRequest(request);
184 request.SetAndOrProcessResponse(returnResult.m_Context[i].CoreResponse);
188 // on error, with more than one callback need to queue others off to another thread
190 GlobalLog.Print("ConnectionReturnResult#" + ValidationHelper.HashString(returnResult) + "::Exception"+e);
191 returnResult.m_Context.RemoveRange(0,(i+1));
192 if (returnResult.m_Context.Count > 0)
194 ThreadPool.UnsafeQueueUserWorkItem(s_InvokeConnectionCallback, returnResult);
200 returnResult.m_Context.Clear();
203 private static void InvokeConnectionCallback(object objectReturnResult)
205 ConnectionReturnResult returnResult = (ConnectionReturnResult)objectReturnResult;
206 SetResponses(returnResult);
211 // Connection - this is the Connection used to parse
212 // server responses, queue requests, and pipeline requests
214 internal class Connection : PooledStream {
217 // thread statics - these values must be per thread, because
218 // other requests and operations can take place concurrently on this Connection.
219 // Our concern is to make sure that a nested call does not get confused with an
220 // operation on another thread. Parameter passing cannot be used, because
221 // the call stack may exit and then reenter the same Connection object.
224 private static int t_SyncReadNesting;
226 private const int CRLFSize = 2;
227 private const long c_InvalidContentLength = -2L;
230 // Buffer manager that allocates and reuses 4k buffers.
232 private const int CachedBufferSize = 4096;
233 private static PinnableBufferCache s_PinnableBufferCache = new PinnableBufferCache("System.Net.Connection", CachedBufferSize);
236 // Little status line holder.
238 private class StatusLineValues
240 internal int MajorVersion;
241 internal int MinorVersion;
242 internal int StatusCode;
243 internal string StatusDescription;
246 private class WaitListItem
248 private HttpWebRequest request;
249 private long queueStartTime;
251 public HttpWebRequest Request
253 get { return request; }
256 public long QueueStartTime
258 get { return queueStartTime; }
261 public WaitListItem(HttpWebRequest request, long queueStartTime)
263 this.request = request;
264 this.queueStartTime = queueStartTime;
271 private WebExceptionStatus m_Error;
272 internal Exception m_InnerException;
275 internal int m_IISVersion = -1; //-1 means unread
276 private byte[] m_ReadBuffer;
277 private bool m_ReadBufferFromPinnableCache; // If we get our m_readBuffer from the Pinnable cache we have to explicitly free it
278 private int m_BytesRead;
279 private int m_BytesScanned;
280 private int m_TotalResponseHeadersLength;
281 private int m_MaximumResponseHeadersLength;
282 private long m_MaximumUnauthorizedUploadLength;
283 private CoreResponseData m_ResponseData;
284 private ReadState m_ReadState;
285 private StatusLineValues m_StatusLineValues;
286 private int m_StatusState;
287 private List<WaitListItem> m_WaitList;
288 private ArrayList m_WriteList;
289 private IAsyncResult m_LastAsyncResult;
290 private TimerThread.Timer m_RecycleTimer;
291 private WebParseError m_ParseError;
292 private bool m_AtLeastOneResponseReceived;
294 private static readonly WaitCallback m_PostReceiveDelegate = new WaitCallback(PostReceiveWrapper);
295 private static readonly AsyncCallback m_ReadCallback = new AsyncCallback(ReadCallbackWrapper);
296 private static readonly AsyncCallback m_TunnelCallback = new AsyncCallback(TunnelThroughProxyWrapper);
297 private static byte[] s_NullBuffer = new byte[0];
300 // Abort handling variables. When trying to abort the
301 // connection, we set Aborted = true, and close m_AbortSocket
302 // if its non-null. m_AbortDelegate, is returned to every
303 // request from our SubmitRequest method. Calling m_AbortDelegate
304 // drives us into Abort mode.
306 private HttpAbortDelegate m_AbortDelegate;
307 private ConnectionGroup m_ConnectionGroup;
309 private UnlockConnectionDelegate m_ConnectionUnlock;
312 // ReadDone and m_Write - no two vars are so complicated,
313 // as these two. Used for m_WriteList managment, most be under crit
314 // section when accessing.
316 // ReadDone tracks the item at the end or
317 // just recenlty removed from the m_WriteList. While a
318 // pending BeginRead is in place, we need this to be false, in
319 // order to indicate to tell the WriteDone callback, that we can
320 // handle errors/resets. The only exception is when the m_WriteList
321 // is empty, and there are no outstanding requests, then all it can
324 // WriteDone tracks the item just added at the begining of the m_WriteList.
325 // this needs to be false while we about to write something, but have not
326 // yet begin or finished the write. Upon completion, its set to true,
327 // so that DoneReading/ReadStartNextRequest can close the socket, without fear
328 // of a errand writer still banging away on another thread.
331 private DateTime m_IdleSinceUtc;
332 private HttpWebRequest m_LockedRequest;
333 private HttpWebRequest m_CurrentRequest; // This is the request whose response is being parsed, same as WriteList[0] but could be different if request was aborted.
334 private bool m_CanPipeline;
335 private bool m_Free = true;
336 private bool m_Idle = true;
337 private bool m_KeepAlive = true;
338 private bool m_Pipelining;
339 private int m_ReservedCount;
340 private bool m_ReadDone;
341 private bool m_WriteDone;
342 private bool m_RemovedFromConnectionList;
343 private bool m_NonKeepAliveRequestPipelined;
345 // Pipeline Throttling: m_IsPipelinePaused==true when we stopped and false when it's ok to add to the pipeline.
346 private bool m_IsPipelinePaused;
347 private static int s_MaxPipelinedCount = 10;
348 private static int s_MinPipelinedCount = 5;
351 private bool q_Tunnelling;
354 internal override ServicePoint ServicePoint {
356 return ConnectionGroup.ServicePoint;
360 private ConnectionGroup ConnectionGroup {
362 return m_ConnectionGroup;
367 // LockedRequest is the request that needs exclusive access to this connection
368 // the ConnectionGroup should proctect the Connection object from any new
369 // Requests being queued, until this m_LockedRequest is finished.
371 internal HttpWebRequest LockedRequest {
373 return m_LockedRequest;
376 HttpWebRequest myLock = m_LockedRequest;
378 GlobalLog.Print("Connection#" + ValidationHelper.HashString(this) + "::LockedRequest_set() old#"+ ((myLock!=null)?myLock.GetHashCode().ToString():"null") + " new#" + ((value!=null)?value.GetHashCode().ToString():"null"));
380 if ((object)value == (object)myLock)
382 if (value != null && (object)value.UnlockConnectionDelegate != (object) m_ConnectionUnlock)
384 throw new InternalException();
389 object myDelegate = myLock == null? null: myLock.UnlockConnectionDelegate;
390 if (myDelegate != null && (value != null || (object)m_ConnectionUnlock != (object)myDelegate))
391 throw new InternalException();
395 m_LockedRequest = null;
396 myLock.UnlockConnectionDelegate = null;
400 UnlockConnectionDelegate chkDelegate = value.UnlockConnectionDelegate;
402 // If "value" request was already locking a connection that is not "this", unlock that other connection
404 if ((object)chkDelegate != null)
406 if ((object)chkDelegate == (object)m_ConnectionUnlock)
407 throw new InternalException();
409 GlobalLog.Print("Connection#" + ValidationHelper.HashString(this) + "::LockedRequest_set() Unlocking old request Connection");
413 value.UnlockConnectionDelegate = m_ConnectionUnlock;
414 m_LockedRequest = value;
421 /// Delegate called when the request is finished using this Connection
422 /// exclusively. Called in Abort cases and after NTLM authenticaiton completes.
425 private void UnlockRequest() {
426 GlobalLog.Print("Connection#" + ValidationHelper.HashString(this) + "::UnlockRequest() LockedRequest#" + ValidationHelper.HashString(LockedRequest));
428 LockedRequest = null;
430 if (ConnectionGroup != null) {
431 GlobalLog.Print("Connection#" + ValidationHelper.HashString(this) + "::UnlockRequest() - forcing call to ConnectionGoneIdle()");
432 ConnectionGroup.ConnectionGoneIdle();
439 private string MyLocalEndPoint {
442 return NetworkStream.InternalSocket.LocalEndPoint.ToString();
445 return "no connection";
451 private string MyLocalPort {
454 if (NetworkStream == null || !NetworkStream.Connected) {
455 return "no connection";
457 return ((IPEndPoint)NetworkStream.InternalSocket.LocalEndPoint).Port.ToString();
460 return "no connection";
466 internal Connection(ConnectionGroup connectionGroup) : base(null) {
468 // add this Connection to the pool in the connection group,
469 // keep a weak reference to it
471 m_MaximumUnauthorizedUploadLength = SettingsSectionInternal.Section.MaximumUnauthorizedUploadLength;
472 if(m_MaximumUnauthorizedUploadLength > 0){
473 m_MaximumUnauthorizedUploadLength*=1024;
475 m_ResponseData = new CoreResponseData();
476 m_ConnectionGroup = connectionGroup;
477 m_ReadBuffer = s_PinnableBufferCache.AllocateBuffer();
478 m_ReadBufferFromPinnableCache = true;
479 m_ReadState = ReadState.Start;
480 m_WaitList = new List<WaitListItem>();
481 m_WriteList = new ArrayList();
482 m_AbortDelegate = new HttpAbortDelegate(AbortOrDisassociate);
483 m_ConnectionUnlock = new UnlockConnectionDelegate(UnlockRequest);
485 // for status line parsing
486 m_StatusLineValues = new StatusLineValues();
487 m_RecycleTimer = ConnectionGroup.ServicePoint.ConnectionLeaseTimerQueue.CreateTimer();
488 // the following line must be the last line of the constructor
489 ConnectionGroup.Associate(this);
492 m_Error = WebExceptionStatus.Success;
493 if (PinnableBufferCacheEventSource.Log.IsEnabled())
495 PinnableBufferCacheEventSource.Log.DebugMessage1("CTOR: In System.Net.Connection.Connnection", this.GetHashCode());
500 if (m_ReadBufferFromPinnableCache)
502 if (PinnableBufferCacheEventSource.Log.IsEnabled())
504 PinnableBufferCacheEventSource.Log.DebugMessage1("DTOR: ERROR Needing to Free m_ReadBuffer in Connection Destructor", m_ReadBuffer.GetHashCode());
510 // If the buffer came from the the pinnable cache, return it to the cache.
511 // NOTE: This method is called from this object's finalizer and should not access any member objects.
512 void FreeReadBuffer() {
513 if (m_ReadBufferFromPinnableCache) {
514 s_PinnableBufferCache.FreeBuffer(m_ReadBuffer);
515 m_ReadBufferFromPinnableCache = false;
520 protected override void Dispose(bool disposing) {
521 if (PinnableBufferCacheEventSource.Log.IsEnabled()) {
522 PinnableBufferCacheEventSource.Log.DebugMessage1("In System.Net.Connection.Dispose()", this.GetHashCode());
525 base.Dispose(disposing);
528 internal int BusyCount {
530 return (m_ReadDone?0:1) + 2 * (m_WaitList.Count + m_WriteList.Count) + m_ReservedCount;
534 internal int IISVersion{
540 internal bool AtLeastOneResponseReceived {
542 return m_AtLeastOneResponseReceived;
548 SubmitRequest - Submit a request for sending.
550 The core submit handler. This is called when a request needs to be
551 submitted to the network. This routine is asynchronous; the caller
552 passes in an HttpSubmitDelegate that we invoke when the caller
553 can use the underlying network. The delegate is invoked with the
554 stream that it can right to.
556 On the Sync path, we work by attempting to gain control of the Connection
557 for writing and reading. If some other thread is using the Connection,
558 We wait inside of a LazyAsyncResult until it is availble.
562 request - request that's being submitted.
563 SubmitDelegate - Delegate to be invoked.
564 forcedsubmit - Queue the request even if connection is going to close.
567 true when the request was correctly submitted
570 // userReqeustThread says whether we can post IO from this thread or not.
571 [SuppressMessage("Microsoft.Reliability","CA2002:DoNotLockOnObjectsWithWeakIdentity", Justification="Re-Baseline System violations from 3.5 SP1 due to added parameter")]
572 internal bool SubmitRequest(HttpWebRequest request, bool forcedsubmit)
574 GlobalLog.Enter("Connection#" + ValidationHelper.HashString(this) + "::SubmitRequest", "request#" + ValidationHelper.HashString(request));
575 GlobalLog.ThreadContract(ThreadKinds.Unknown, "Connection#" + ValidationHelper.HashString(this) + "::SubmitRequest");
576 GlobalLog.Print("Connection#" + ValidationHelper.HashString(this) + "::SubmitRequest() Free:" + m_Free.ToString() + " m_WaitList.Count:" + m_WaitList.Count.ToString());
578 TriState startRequestResult = TriState.Unspecified;
579 ConnectionReturnResult returnResult = null;
580 bool expiredIdleConnection = false;
582 // See if the connection is free, and if the underlying socket or
583 // stream is set up. If it is, we can assign this connection to the
584 // request right now. Otherwise we'll have to put this request on
585 // on the wait list until it its turn.
589 request.AbortDelegate = m_AbortDelegate;
593 // Note that request is not on the connection list yet and Abort() will push the response on the request
594 GlobalLog.Leave("Connection#" + ValidationHelper.HashString(this) + "::SubmitRequest - (Request was aborted before being submitted)", true);
595 UnlockIfNeeded(request);
599 // There is a race condition between FindConnection and PrepareCloseConnectionSocket
600 // Some request may already try to submit themselves while the connection is dying.
602 // Retry if that's the case
606 GlobalLog.Leave("Connection#" + ValidationHelper.HashString(this) + "::SubmitRequest", "false - can't be pooled");
607 UnlockIfNeeded(request);
612 // There is a race condition between SubmitRequest and FindConnection. A non keep-alive request may
613 // get submitted on this connection just after we check for it. So make sure that if we are queueing
614 // behind non keep-alive request then its a forced submit.
615 // Retry if that's not the case.
617 if (!forcedsubmit && NonKeepAliveRequestPipelined)
619 GlobalLog.Leave("Connection#" + ValidationHelper.HashString(this) + "::SubmitRequest", "false - behind non keep-alive request");
620 UnlockIfNeeded(request);
624 // See if our timer still matches the SerivcePoint. If not, get rid of it.
625 if (m_RecycleTimer.Duration != ServicePoint.ConnectionLeaseTimerQueue.Duration) {
626 m_RecycleTimer.Cancel();
627 m_RecycleTimer = ServicePoint.ConnectionLeaseTimerQueue.CreateTimer();
630 if (m_RecycleTimer.HasExpired) {
631 request.KeepAlive = false;
635 // If the connection has already been locked by another request, then
636 // we fail the submission on this Connection.
639 if (LockedRequest != null && LockedRequest != request) {
640 GlobalLog.Leave("Connection#" + ValidationHelper.HashString(this) + "::SubmitRequest", "false");
645 //free means no one in the wait list. We should only add a request
646 //if the request can pipeline, or pipelining isn't available
648 GlobalLog.Print("Connection#" + ValidationHelper.HashString(this) + "::SubmitRequest WriteDone:" + m_WriteDone.ToString() + ", ReadDone:" + m_ReadDone.ToString() + ", m_WriteList.Count:" + m_WriteList.Count.ToString());
651 // If this request is marked as non keep-alive, we should stop pipelining more requests on this
652 // connection. The keep-alive context is transfered to the connection from request only after we start
653 // receiving response for the request.
655 if (!forcedsubmit && !m_NonKeepAliveRequestPipelined) {
656 m_NonKeepAliveRequestPipelined = (!request.KeepAlive && !request.NtlmKeepAlive);
659 if (m_Free && m_WriteDone && !forcedsubmit && (m_WriteList.Count == 0 || (request.Pipelined && !request.HasEntityBody && m_CanPipeline && m_Pipelining && !m_IsPipelinePaused))) {
661 // Connection is free. Mark it as busy and see if underlying
663 GlobalLog.Print("Connection#" + ValidationHelper.HashString(this) + "::SubmitRequest - Free ");
666 // This codepath handles the case where the server has closed the Connection by
667 // returning false below: the request will be resubmitted on a different Connection.
668 startRequestResult = StartRequest(request, true);
669 if (startRequestResult == TriState.Unspecified)
671 expiredIdleConnection = true;
672 PrepareCloseConnectionSocket(ref returnResult);
673 // Hard Close the socket.
675 FreeReadBuffer(); // Do it after close completes to insure buffer not in use
679 m_WaitList.Add(new WaitListItem(request, NetworkingPerfCounters.GetTimestamp()));
680 NetworkingPerfCounters.Instance.Increment(NetworkingPerfCounterName.HttpWebRequestQueued);
681 GlobalLog.Print("Connection#" + ValidationHelper.HashString(this) + "::SubmitRequest - Request added to WaitList#"+ValidationHelper.HashString(request));
684 GlobalLog.Print("Connection#" + ValidationHelper.HashString(this) + "::SubmitRequest() MyLocalPort:" + MyLocalPort + " ERROR: adding HttpWebRequest#" + ValidationHelper.HashString(request) +" to tunnelling WaitList");
687 GlobalLog.Print("Connection#" + ValidationHelper.HashString(this) + "::SubmitRequest() MyLocalPort:" + MyLocalPort + " adding HttpWebRequest#" + ValidationHelper.HashString(request) +" to non-tunnelling WaitList m_WaitList.Count:" + m_WaitList.Count);
694 if (expiredIdleConnection)
696 GlobalLog.Leave("Connection#" + ValidationHelper.HashString(this) + "::SubmitRequest(), expired idle connection", false);
697 ConnectionReturnResult.SetResponses(returnResult);
701 GlobalLog.DebugAddRequest(request, this, 0);
702 if(Logging.On)Logging.Associate(Logging.Web, this, request);
704 if (startRequestResult != TriState.Unspecified) {
705 CompleteStartRequest(true, request, startRequestResult);
707 // On [....], we wait for the Connection to be come availble here,
710 object responseObject = request.ConnectionAsyncResult.InternalWaitForCompletion();
711 ConnectStream writeStream = responseObject as ConnectStream;
712 AsyncTriState triStateAsync = null;
713 if (writeStream == null)
714 triStateAsync = responseObject as AsyncTriState;
716 GlobalLog.Print("Connection#" + ValidationHelper.HashString(this) + "::SubmitRequest() Pipelining:"+m_Pipelining);
718 if (startRequestResult == TriState.Unspecified && triStateAsync != null) {
719 // May need to recreate Connection here (i.e. call Socket.Connect)
720 CompleteStartRequest(true, request, triStateAsync.Value);
722 else if (writeStream != null)
724 // return the Stream to the Request
725 request.SetRequestSubmitDone(writeStream);
728 else if (responseObject is Exception)
730 Exception exception = responseObject as Exception;
731 WebException webException = responseObject as WebException;
732 GlobalLog.Leave("Connection#" + ValidationHelper.HashString(this) + "::SubmitRequest (SYNC) - Error waiting for a connection: " + exception.Message,
733 "Status:" + (webException == null? exception.GetType().FullName: (webException.Status.ToString() + " Internal Status: " + webException.InternalStatus.ToString())));
739 GlobalLog.Leave("Connection#" + ValidationHelper.HashString(this) + "::SubmitRequest", true);
743 private void UnlockIfNeeded(HttpWebRequest request) {
744 if (LockedRequest == request) {
749 // Wrapper for TriState for marhshalling across Thread boundaries
750 private class AsyncTriState {
751 public TriState Value;
752 public AsyncTriState(TriState newValue) {
759 StartRequest - Start a request going.
761 Routine to start a request. Called when we know the connection is
762 free and we want to get a request going. This routine initializes
763 some state, adds the request to the write queue, and checks to
764 see whether or not the underlying connection is alive. If it's
765 not, it queues a request to get it going. If the connection
766 was alive we call the callback delegate of the request.
768 This routine MUST BE called with the critcal section held.
771 request - request that's being started.
772 canPollRead - whether the calling code handles
773 Unspecified due to the Connection
774 being closed by the server.
777 True if request was started, false otherwise.
781 private TriState StartRequest(HttpWebRequest request, bool canPollRead)
784 "Connection#" + ValidationHelper.HashString(this) +
786 "HttpWebRequest#" + ValidationHelper.HashString(request) +
787 " WriteDone:"+ m_WriteDone +
788 " ReadDone:" + m_ReadDone +
789 " WaitList:" + m_WaitList.Count +
790 " WriteList:" + m_WriteList.Count);
791 GlobalLog.ThreadContract(ThreadKinds.Unknown,
792 "Connection#" + ValidationHelper.HashString(this) +
795 if (m_WriteList.Count == 0)
797 // check if we consider connection timed out
798 if (ServicePoint.MaxIdleTime != -1 &&
799 m_IdleSinceUtc != DateTime.MinValue &&
801 TimeSpan.FromMilliseconds(ServicePoint.MaxIdleTime) <
804 // This idle keep-alive connection timed out.
806 "Connection#" + ValidationHelper.HashString(this) +
808 " Expired connection was idle for "
810 ((DateTime.UtcNow - m_IdleSinceUtc).TotalSeconds) +
811 " secs, request will be retried: #" +
812 ValidationHelper.HashString(request));
813 return TriState.Unspecified; // don't use it
814 } else if (canPollRead) {
815 // Not timed out from our perspective but...
816 // Check if remote has:
817 // 1) closed an idle connection (TCP FIN)
819 // 2) sent some errant data on an idle connection.
820 bool pollRead = PollRead();
823 "Connection#" + ValidationHelper.HashString(this) +
824 "::StartRequest() " +
825 "Idle connection remotely closed, " +
826 "request will be retried: #" +
827 ValidationHelper.HashString(request));
828 return TriState.Unspecified; // don't use it
833 TriState needReConnect = TriState.False;
834 // Starting a request means the connection is not idle anymore
835 m_IdleSinceUtc = DateTime.MinValue;
837 // Initialze state, and add the request to the write queue.
840 // Note that m_Pipelining shold be only set here but the sanity check is made by the caller
841 // means if the caller has found that it is safe to pipeline the below result must be true as well
843 if (!m_IsPipelinePaused)
844 m_IsPipelinePaused = m_WriteList.Count >= s_MaxPipelinedCount;
846 m_Pipelining = m_CanPipeline && request.Pipelined && (!request.HasEntityBody);
848 // start of write process, disable done-ness flag
849 GlobalLog.Print("Connection#" + ValidationHelper.HashString(this) + "::StartRequest() setting WriteDone:" + m_WriteDone.ToString() + " to false");
851 GlobalLog.Print("Connection#" + ValidationHelper.HashString(this) + "::StartRequest() m_WriteList adding HttpWebRequest#" + ValidationHelper.HashString(request));
852 m_WriteList.Add(request);
854 GlobalLog.Print(m_WriteList.Count+" requests queued");
857 // with no network stream around, we will have to create one, therefore, we can't have
858 // the possiblity to even have a DoneReading().
861 needReConnect = TriState.True;
864 if (request.IsTunnelRequest) {
865 GlobalLog.Print("Connection#" + ValidationHelper.HashString(this) + "::StartRequest() MyLocalPort:" + MyLocalPort + " setting Tunnelling to true HttpWebRequest#" + ValidationHelper.HashString(request));
869 GlobalLog.Assert(!q_Tunnelling, "Connection#{0}::StartRequest()|MyLocalPort:{1} ERROR: Already tunnelling during non-tunnel request HttpWebRequest#{2}.", ValidationHelper.HashString(this), MyLocalPort, ValidationHelper.HashString(request));
873 GlobalLog.Leave("Connection#" + ValidationHelper.HashString(this) + "::StartRequest", needReConnect.ToString());
874 return needReConnect;
877 private void CompleteStartRequest(bool onSubmitThread, HttpWebRequest request, TriState needReConnect) {
878 GlobalLog.Enter("Connection#" + ValidationHelper.HashString(this) + "::CompleteStartRequest", ValidationHelper.HashString(request));
879 GlobalLog.ThreadContract(ThreadKinds.Unknown, "Connection#" + ValidationHelper.HashString(this) + "::CompleteStartRequest");
881 if (needReConnect == TriState.True) {
882 // Socket is not alive.
884 GlobalLog.Print("Connection#" + ValidationHelper.HashString(this) + "::CompleteStartRequest() Queue StartConnection Delegate ");
887 CompleteStartConnection(true, request);
889 else if (onSubmitThread) {
890 CompleteStartConnection(false, request);
892 // else - fall through and wake up other thread
894 catch (Exception exception) {
895 GlobalLog.Print("Connection#" + ValidationHelper.HashString(this) + "::CompleteStartRequest(): exception: " + exception.ToString());
896 if (NclUtilities.IsFatal(exception)) throw;
898 // Should not be here because CompleteStartConnection and below tries to catch everything
900 GlobalLog.Assert(exception.ToString());
903 // If neeeded wake up other thread where SubmitRequest was called
904 if (!request.Async) {
905 GlobalLog.Print("Connection#" + ValidationHelper.HashString(this) + "::CompleteStartRequest() Invoking Async Result");
906 request.ConnectionAsyncResult.InvokeCallback(new AsyncTriState(needReConnect));
910 GlobalLog.Leave("Connection#" + ValidationHelper.HashString(this) + "::CompleteStartRequest", "needReConnect");
916 // From now on the request.SetRequestSubmitDone must be called or it may hang
917 // For a [....] request the write side reponse windowwas opened in HttpWebRequest.SubmitRequest
919 request.OpenWriteSideResponseWindow();
922 ConnectStream writeStream = new ConnectStream(this, request);
924 // Call the request to let them know that we have a write-stream, this might invoke Send() call
925 if (request.Async || onSubmitThread) {
926 request.SetRequestSubmitDone(writeStream);
929 GlobalLog.Print("Connection#" + ValidationHelper.HashString(this) + "::CompleteStartRequest() Invoking Async Result");
930 request.ConnectionAsyncResult.InvokeCallback(writeStream);
932 GlobalLog.Leave("Connection#" + ValidationHelper.HashString(this) + "::CompleteStartRequest");
939 Gets the next request from the wait queue, if there is one.
941 Must be called with the crit sec held.
945 private HttpWebRequest CheckNextRequest()
947 GlobalLog.ThreadContract(ThreadKinds.Unknown, "Connection#" + ValidationHelper.HashString(this) + "::CheckNextRequest");
949 if (m_WaitList.Count == 0) {
950 // We're free now, if we're not going to close the connection soon.
951 m_Free = m_KeepAlive;
958 WaitListItem item = m_WaitList[0];
959 HttpWebRequest nextRequest = item.Request;
961 if (m_IsPipelinePaused)
962 m_IsPipelinePaused = m_WriteList.Count > s_MinPipelinedCount;
964 if (!nextRequest.Pipelined || nextRequest.HasEntityBody || !m_CanPipeline || !m_Pipelining || m_IsPipelinePaused) {
965 if (m_WriteList.Count != 0) {
969 if (nextRequest != null) {
970 GlobalLog.Print("Connection#" + ValidationHelper.HashString(this) + "::CheckNextRequest() Removing request#" + ValidationHelper.HashString(nextRequest) + " from m_WaitList. New Count:" + (m_WaitList.Count - 1).ToString());
972 NetworkingPerfCounters.Instance.IncrementAverage(NetworkingPerfCounterName.HttpWebRequestAvgQueueTime,
973 item.QueueStartTime);
974 m_WaitList.RemoveAt(0);
980 private void CompleteStartConnection(bool async, HttpWebRequest httpWebRequest)
982 GlobalLog.Enter("Connection#" + ValidationHelper.HashString(this) + "::CompleteStartConnection", "async:" + async.ToString() + " httpWebRequest:" + ValidationHelper.HashString(httpWebRequest));
983 GlobalLog.ThreadContract(ThreadKinds.Unknown, "Connection#" + ValidationHelper.HashString(this) + "::CompleteStartConnection");
985 WebExceptionStatus ws = WebExceptionStatus.ConnectFailure;
986 m_InnerException = null;
993 // m_WriteList can be empty if request got aborted. In that case no new requests can come in so it should remain zero.
994 if (m_WriteList.Count != 0)
996 GlobalLog.Assert(m_WriteList.Count == 1, "Connection#{0}::CompleteStartConnection()|WriteList is not sized 1.", ValidationHelper.HashString(this));
997 GlobalLog.Assert((m_WriteList[0] as HttpWebRequest) == httpWebRequest, "Connection#{0}::CompleteStartConnection()|Last request on write list does not match.", ValidationHelper.HashString(this));
1003 // we will create a tunnel through a proxy then create
1004 // and connect the socket we will use for the connection
1005 // otherwise we will just create a socket and use it
1007 if ((httpWebRequest.IsWebSocketRequest || httpWebRequest.Address.Scheme == Uri.UriSchemeHttps) &&
1008 ServicePoint.InternalProxyServicePoint)
1010 if(!TunnelThroughProxy(ServicePoint.InternalAddress, httpWebRequest,async)) {
1011 ws = WebExceptionStatus.ConnectFailure;
1014 if (async && success) {
1018 if (!Activate(httpWebRequest, async, new GeneralAsyncDelegate(CompleteConnectionWrapper)))
1024 catch (Exception exception) {
1025 if (m_InnerException == null)
1026 m_InnerException = exception;
1028 if (exception is WebException) {
1029 ws = ((WebException)exception).Status;
1035 ConnectionReturnResult returnResult = null;
1036 HandleError(false, false, ws, ref returnResult);
1037 ConnectionReturnResult.SetResponses(returnResult);
1038 GlobalLog.Leave("Connection#" + ValidationHelper.HashString(this) + "::CompleteStartConnection Failed to connect.");
1042 // Getting here means we connected synchronously. Continue with the next step.
1044 CompleteConnection(async, httpWebRequest);
1045 GlobalLog.Leave("Connection#" + ValidationHelper.HashString(this) + "::CompleteStartConnection");
1048 private void CompleteConnectionWrapper(object request, object state)
1051 using (GlobalLog.SetThreadKind(ThreadKinds.System | ThreadKinds.Async)) {
1053 GlobalLog.Enter("Connection#" + ValidationHelper.HashString(state) + "::CompleteConnectionWrapper", "request:" + ValidationHelper.HashString(request));
1055 Exception stateException = state as Exception;
1056 if (stateException != null)
1058 GlobalLog.Print("CompleteConnectionWrapper() Request#" + ValidationHelper.HashString(request) + " Connection is in error: " + stateException.ToString());
1059 ConnectionReturnResult returnResult = null;
1061 if (m_InnerException == null)
1062 m_InnerException = stateException;
1064 HandleError(false, false, WebExceptionStatus.ConnectFailure, ref returnResult);
1065 ConnectionReturnResult.SetResponses(returnResult);
1067 CompleteConnection(true, (HttpWebRequest) request);
1069 GlobalLog.Leave("Connection#" + ValidationHelper.HashString(state) + "::CompleteConnectionWrapper" + (stateException == null? string.Empty: " failed"));
1075 private void CompleteConnection(bool async, HttpWebRequest request)
1077 GlobalLog.Enter("Connection#" + ValidationHelper.HashString(this) + "::CompleteConnection", "async:" + async.ToString() + " request:" + ValidationHelper.HashString(request));
1078 GlobalLog.ThreadContract(ThreadKinds.Unknown, "Connection#" + ValidationHelper.HashString(this) + "::CompleteConnection");
1080 WebExceptionStatus ws = WebExceptionStatus.ConnectFailure;
1082 // From now on the request.SetRequestSubmitDone must be called or it may hang
1083 // For a [....] request the write side reponse windowwas opened in HttpWebRequest.SubmitRequest
1085 request.OpenWriteSideResponseWindow();
1091 if (request.Address.Scheme == Uri.UriSchemeHttps) {
1092 TlsStream tlsStream = new TlsStream(request.GetRemoteResourceUri().IdnHost,
1093 NetworkStream, request.ClientCertificates, ServicePoint, request,
1094 request.Async ? request.GetConnectingContext().ContextCopy : null);
1095 NetworkStream = tlsStream;
1098 ws = WebExceptionStatus.Success;
1101 // The TLS stream could not be created. Close the current non-TLS stream immediately
1102 // to prevent any future use of it. Due to race conditions, the error handling will sometimes
1103 // try to write (flush) out some of the HTTP headers to the stream as it is closing down the failed
1104 // HttpWebRequest. This would cause plain text to go on the wire even though the stream should
1105 // have been TLS encrypted.
1106 NetworkStream.Close();
1111 // There is a ---- with Abort so TlsStream ctor may throw.
1112 // SetRequestSubmitDone will deal with this kind of errors.
1115 m_ReadState = ReadState.Start;
1118 request.SetRequestSubmitDone(new ConnectStream(this, request));
1121 catch (Exception exception)
1123 if (m_InnerException == null)
1124 m_InnerException = exception;
1125 WebException webException = exception as WebException;
1126 if (webException != null)
1128 ws = webException.Status;
1132 if (ws != WebExceptionStatus.Success)
1134 ConnectionReturnResult returnResult = null;
1135 HandleError(false, false, ws, ref returnResult);
1136 ConnectionReturnResult.SetResponses(returnResult);
1138 if (Logging.On) Logging.PrintError(Logging.Web, this, "CompleteConnection", "on error");
1139 GlobalLog.Leave("Connection#" + ValidationHelper.HashString(this) + "::CompleteConnection", "on error");
1143 GlobalLog.Leave("Connection#" + ValidationHelper.HashString(this) + "::CompleteConnection");
1147 private void InternalWriteStartNextRequest(HttpWebRequest request, ref bool calledCloseConnection, ref TriState startRequestResult, ref HttpWebRequest nextRequest, ref ConnectionReturnResult returnResult) {
1148 GlobalLog.ThreadContract(ThreadKinds.Unknown, "Connection#" + ValidationHelper.HashString(this) + "::InternalWriteStartNextRequest");
1152 GlobalLog.Print("Connection#" + ValidationHelper.HashString(this) + "::WriteStartNextRequest() setting WriteDone:" + m_WriteDone.ToString() + " to true");
1156 // If we're not doing keep alive, and the read on this connection
1157 // has already completed, now is the time to close the
1160 //need to wait for read to set the error
1161 if (!m_KeepAlive || m_Error != WebExceptionStatus.Success || !CanBePooled) {
1162 GlobalLog.Print("Connection#" + ValidationHelper.HashString(this) + "::WriteStartNextRequest() m_WriteList.Count:" + m_WriteList.Count);
1164 // We could be closing because of an unexpected keep-alive
1165 // failure, ie we pipelined a few requests and in the middle
1166 // the remote server stopped doing keep alive. In this
1167 // case m_Error could be success, which would be misleading.
1168 // So in that case we'll set it to connection closed.
1170 if (m_Error == WebExceptionStatus.Success) {
1171 // Only reason we could have gotten here is because
1172 // we're not keeping the connection alive.
1173 m_Error = WebExceptionStatus.KeepAliveFailure;
1176 // PrepareCloseConnectionSocket is called with the critical section
1177 // held. Note that we know since it's not a keep-alive
1178 // connection the read half wouldn't have posted a receive
1179 // for this connection, so it's OK to call PrepareCloseConnectionSocket now.
1180 PrepareCloseConnectionSocket(ref returnResult);
1181 calledCloseConnection = true;
1185 if (m_Error!=WebExceptionStatus.Success) {
1186 GlobalLog.Print("Connection#" + ValidationHelper.HashString(this) + "::WriteStartNextRequest() a Failure, m_Error = " + m_Error.ToString());
1191 // If we're pipelining, we get get the next request going
1192 // as soon as the write is done. Otherwise we have to wait
1193 // until both read and write are done.
1196 GlobalLog.Print("Connection#" + ValidationHelper.HashString(this) + "::WriteStartNextRequest() Non-Error m_WriteList.Count:" + m_WriteList.Count + " m_WaitList.Count:" + m_WaitList.Count);
1198 if (m_Pipelining || m_ReadDone)
1200 nextRequest = CheckNextRequest();
1202 if (nextRequest != null)
1204 // This codepath doesn't handle the case where the server has closed the Connection because we
1205 // just finished using it and didn't get a Connection: close header.
1206 startRequestResult = StartRequest(nextRequest, false);
1207 GlobalLog.Assert(startRequestResult != TriState.Unspecified, "WriteStartNextRequest got TriState.Unspecified from StartRequest, things are about to hang!");
1213 internal void WriteStartNextRequest(HttpWebRequest request, ref ConnectionReturnResult returnResult) {
1214 GlobalLog.Enter("Connection#" + ValidationHelper.HashString(this) + "::WriteStartNextRequest" + " WriteDone:" + m_WriteDone + " ReadDone:" + m_ReadDone + " WaitList:" + m_WaitList.Count + " WriteList:" + m_WriteList.Count);
1215 GlobalLog.ThreadContract(ThreadKinds.Unknown, "Connection#" + ValidationHelper.HashString(this) + "::WriteStartNextRequest");
1217 TriState startRequestResult = TriState.Unspecified;
1218 HttpWebRequest nextRequest = null;
1219 bool calledCloseConnection = false;
1221 InternalWriteStartNextRequest(request, ref calledCloseConnection, ref startRequestResult, ref nextRequest, ref returnResult);
1223 GlobalLog.Print("Connection#" + ValidationHelper.HashString(this) + "::WriteStartNextRequest: Pipelining:" + m_Pipelining + " nextRequest#"+ValidationHelper.HashString(nextRequest));
1225 if (!calledCloseConnection && startRequestResult != TriState.Unspecified)
1227 GlobalLog.Print("Connection#" + ValidationHelper.HashString(this) + "::WriteStartNextRequest calling CompleteStartRequest");
1228 CompleteStartRequest(false, nextRequest, startRequestResult);
1231 GlobalLog.Leave("Connection#" + ValidationHelper.HashString(this) + "::WriteStartNextRequest");
1235 internal void SetLeftoverBytes(byte[] buffer, int bufferOffset, int bufferCount)
1237 // The ConnectStream read past the response of its HTTP response (can happen in chunked scenarios).
1238 // Get the buffer containing bytes belonging to the next request and use them for the next request.
1240 if (bufferOffset > 0)
1242 // We need to move leftover bytes to the beginning of the buffer.
1243 Buffer.BlockCopy(buffer, bufferOffset, buffer, 0, bufferCount);
1246 // If we had to reallocate the buffer, we are going to clobber the one that was allocated from the pin friendly cache.
1248 if (m_ReadBuffer != buffer)
1250 // if m_ReadBuffer is from the pinnable cache, give it back
1252 m_ReadBuffer = buffer;
1256 m_BytesRead = bufferCount;
1261 ReadStartNextRequest
1263 This method is called by a stream interface when it's done reading.
1264 We might possible free up the connection for another request here.
1266 Called when we think we might need to start another request because
1270 internal void ReadStartNextRequest(WebRequest currentRequest, ref ConnectionReturnResult returnResult)
1272 GlobalLog.Enter("Connection#" + ValidationHelper.HashString(this) + "::ReadStartNextRequest" + " WriteDone:" + m_WriteDone + " ReadDone:" + m_ReadDone + " WaitList:" + m_WaitList.Count + " WriteList:" + m_WriteList.Count);
1273 GlobalLog.ThreadContract(ThreadKinds.Unknown, "Connection#" + ValidationHelper.HashString(this) + "::ReadStartNextRequest");
1275 HttpWebRequest nextRequest = null;
1276 TriState startRequestResult = TriState.Unspecified;
1277 bool calledCloseConnection = false;
1278 bool mustExit = false;
1280 // ReadStartNextRequest is called by ConnectStream.CallDone: This guarantees that the request
1281 // is done and the response (response stream) was closed. Remove the reservation for the request.
1282 int currentCount = Interlocked.Decrement(ref m_ReservedCount);
1283 GlobalLog.Assert(currentCount >= 0, "m_ReservedCount must not be < 0 when decremented.");
1287 if (m_WriteList.Count > 0 && (object)currentRequest == m_WriteList[0])
1289 // advance back to state 0
1290 m_ReadState = ReadState.Start;
1291 m_WriteList.RemoveAt(0);
1293 // Must reset ConnectStream here to prevent a leak through the stream of the last request on each connection.
1294 m_ResponseData.m_ConnectStream = null;
1296 GlobalLog.Print("Connection#" + ValidationHelper.HashString(this) + "::ReadStartNextRequest() Removed request#" + ValidationHelper.HashString(currentRequest) + " from m_WriteList. New m_WriteList.Count:" + m_WriteList.Count.ToString());
1300 GlobalLog.Print("Connection#" + ValidationHelper.HashString(this) + "::ReadStartNextRequest() The request#" + ValidationHelper.HashString(currentRequest) + " was disassociated so do nothing. m_WriteList.Count:" + m_WriteList.Count.ToString());
1305 // Since this is called after we're done reading the current
1306 // request, if we're not doing keepalive and we're done
1307 // writing we can close the connection now.
1312 // m_ReadDone==true is implied because we just finished a request but really the value must still be false here
1315 throw new InternalException(); // other requests may already started reading on this connection, need a QFE
1317 if (!m_KeepAlive || m_Error != WebExceptionStatus.Success || !CanBePooled)
1319 GlobalLog.Print("Connection#" + ValidationHelper.HashString(this) + "::ReadStartNextRequest() KeepAlive:" + m_KeepAlive + " WriteDone:" + m_WriteDone);
1320 // Finished one request and connection is closing.
1321 // We will not read from this connection so set readDone = true
1327 // We could be closing because of an unexpected keep-alive
1328 // failure, ie we pipelined a few requests and in the middle
1329 // the remote server stopped doing keep alive. In this
1330 // case m_Error could be success, which would be misleading.
1331 // So in that case we'll set it to KeepAliveFailure.
1333 if (m_Error == WebExceptionStatus.Success) {
1334 // Only reason we could have gotten here is because
1335 // we're not keeping the connection alive.
1336 m_Error = WebExceptionStatus.KeepAliveFailure;
1339 // PrepareCloseConnectionSocket has to be called with the critical section held.
1340 PrepareCloseConnectionSocket(ref returnResult);
1341 calledCloseConnection = true;
1347 // We try to sort out KeepAliveFailure thing (search by context)
1348 m_AtLeastOneResponseReceived = true;
1350 if (m_WriteList.Count != 0)
1352 // If a *pipelined* request that is being submitted has finished with the headers, post a receive
1353 nextRequest = m_WriteList[0] as HttpWebRequest;
1354 // If the active request has not finished its headers we can set m_ReadDone = true
1355 // and that will be changed when said request will call CheckStartReceive
1356 if (!nextRequest.HeadersCompleted)
1362 // If there are no requests left to write (means pipeline),
1363 // we can get the next request from wait list going now.
1368 // Sometime we get a response before completing the body in which case
1369 // we defer next request to WriteStartNextRequest
1372 nextRequest = CheckNextRequest();
1374 if (nextRequest != null )
1376 // We cannot have HeadersCompleted on the request that was not placed yet on the write list
1377 if(nextRequest.HeadersCompleted) // TODO: change to be Assert but only when stress got stable-stable
1378 throw new InternalException();
1380 // This codepath doesn't handle the case where the server has closed the
1381 // Connection because we just finished using it and didn't get a
1382 // Connection: close header.
1383 startRequestResult = StartRequest(nextRequest, false);
1384 GlobalLog.Assert(startRequestResult != TriState.Unspecified, "ReadStartNextRequest got TriState.Unspecified from StartRequest, things are about to hang!");
1388 //There are no other requests to process, so make connection avaliable for all
1400 //set result here to prevent nesting of readstartnextrequest.
1401 if(returnResult != null){
1402 ConnectionReturnResult.SetResponses(returnResult);
1406 if(!mustExit && !calledCloseConnection)
1408 if (startRequestResult != TriState.Unspecified)
1410 CompleteStartRequest(false, nextRequest, startRequestResult);
1412 else if (nextRequest != null)
1414 // Handling receive, note that is for pipelinning case only !
1415 if (!nextRequest.Async)
1417 nextRequest.ConnectionReaderAsyncResult.InvokeCallback();
1421 if (m_BytesScanned < m_BytesRead)
1423 GlobalLog.Print("Connection#" + ValidationHelper.HashString(this) + "::ReadStartNextRequest() Calling ReadComplete, bytes unparsed = " + (m_BytesRead - m_BytesScanned));
1424 ReadComplete(0, WebExceptionStatus.Success);
1426 else if (Thread.CurrentThread.IsThreadPoolThread)
1428 GlobalLog.Print("Connection#" + ValidationHelper.HashString(this) + "::ReadStartNextRequest() Calling PostReceive().");
1433 // Offload to the threadpool to protect against the case where one request's thread posts IO that another request
1434 // depends on, but the first thread dies in the mean time.
1435 GlobalLog.Print("Connection#" + ValidationHelper.HashString(this) + "::ReadStartNextRequest() ThreadPool.UnsafeQueueUserWorkItem(m_PostReceiveDelegate, this)");
1436 ThreadPool.UnsafeQueueUserWorkItem(m_PostReceiveDelegate, this);
1441 GlobalLog.Leave("Connection#" + ValidationHelper.HashString(this) + "::ReadStartNextRequest");
1444 internal void MarkAsReserved()
1446 // We use an interlock here rather than a lock() to avoid deadlocks in the following situation:
1447 // - ConnectionGroup is holding lock(obj1), calls into Connection which is waiting for lock(obj2)
1448 // - on another thread Connection is holding lock(obj2) and calls into ConnectionGroup which will wait
1450 int currentCount = Interlocked.Increment(ref m_ReservedCount);
1451 GlobalLog.Assert(currentCount > 0, "m_ReservedCount must not be less or equal zero after incrementing.");
1457 internal void CheckStartReceive(HttpWebRequest request)
1461 request.HeadersCompleted = true;
1462 if (m_WriteList.Count == 0)
1464 // aborted request, was already dispatched.
1465 // Note it could have been aborted softly if not the first one in the pipeline
1469 // Note we do NOT allow receive if pipelining and the passed request is not the first one on the write queue
1470 if (!m_ReadDone || m_WriteList[0] != (object)request)
1472 // ReadStartNextRequest should take care of these cases
1477 m_CurrentRequest = (HttpWebRequest)m_WriteList[0];
1482 GlobalLog.Print("Connection#" + ValidationHelper.HashString(this) + "::CheckStartReceive() SYNC request, calling ConnectionReaderAsyncResult.InvokeCallback()");
1483 request.ConnectionReaderAsyncResult.InvokeCallback();
1485 else if (m_BytesScanned < m_BytesRead)
1487 GlobalLog.Print("Connection#" + ValidationHelper.HashString(this) + "::CheckStartReceive() Calling ReadComplete, bytes unparsed = " + (m_BytesRead - m_BytesScanned));
1488 ReadComplete(0, WebExceptionStatus.Success);
1490 else if (Thread.CurrentThread.IsThreadPoolThread)
1492 GlobalLog.Print("Connection#" + ValidationHelper.HashString(this) + "::CheckStartReceive() Calling PostReceive().");
1497 // Offload to the threadpool to protect against the case where one request's thread posts IO that another request
1498 // depends on, but the first thread dies in the mean time.
1499 GlobalLog.Print("Connection#" + ValidationHelper.HashString(this) + "::CheckStartReceive() ThreadPool.UnsafeQueueUserWorkItem(m_PostReceiveDelegate, this)");
1500 ThreadPool.UnsafeQueueUserWorkItem(m_PostReceiveDelegate, this);
1506 Routine Description:
1508 Clears out common member vars used for Status Line parsing
1520 private void InitializeParseStatusLine() {
1521 m_StatusState = BeforeVersionNumbers;
1522 m_StatusLineValues.MajorVersion = 0;
1523 m_StatusLineValues.MinorVersion = 0;
1524 m_StatusLineValues.StatusCode = 0;
1525 m_StatusLineValues.StatusDescription = null;
1530 Routine Description:
1532 Performs status line parsing on incomming server responses
1536 statusLine - status line that we wish to parse
1537 statusLineLength - length of the array
1538 statusLineInts - array of ints contanes result
1539 statusDescription - string with discription
1540 statusStatus - state stored between parse attempts
1544 bool - Success true/false
1548 private const int BeforeVersionNumbers = 0;
1549 private const int MajorVersionNumber = 1;
1550 private const int MinorVersionNumber = 2;
1551 private const int StatusCodeNumber = 3;
1552 private const int AfterStatusCode = 4;
1553 private const int AfterCarriageReturn = 5;
1555 private const string BeforeVersionNumberBytes = "HTTP/";
1557 private DataParseStatus ParseStatusLine(
1559 int statusLineLength,
1560 ref int bytesParsed,
1561 ref int [] statusLineInts,
1562 ref string statusDescription,
1563 ref int statusState,
1564 ref WebParseError parseError) {
1565 GlobalLog.Enter("Connection#" + ValidationHelper.HashString(this) + "::ParseStatusLine", statusLineLength.ToString(NumberFormatInfo.InvariantInfo) + ", " + bytesParsed.ToString(NumberFormatInfo.InvariantInfo) +", " +statusState.ToString(NumberFormatInfo.InvariantInfo));
1566 GlobalLog.ThreadContract(ThreadKinds.Unknown, "Connection#" + ValidationHelper.HashString(this) + "::ParseStatusLine");
1567 GlobalLog.Assert((statusLineLength - bytesParsed) >= 0, "Connection#{0}::ParseStatusLine()|(statusLineLength - bytesParsed) < 0", ValidationHelper.HashString(this));
1568 //GlobalLog.Dump(statusLine, bytesParsed, statusLineLength);
1570 DataParseStatus parseStatus = DataParseStatus.Done;
1571 int statusLineSize = 0;
1572 int startIndexStatusDescription = -1;
1573 int lastUnSpaceIndex = 0;
1576 // While walking the Status Line looking for terminating \r\n,
1577 // we extract the Major.Minor Versions and Status Code in that order.
1578 // text and spaces will lie between/before/after the three numbers
1579 // but the idea is to remember which number we're calculating based on a numeric state
1580 // If all goes well the loop will churn out an array with the 3 numbers plugged in as DWORDs
1583 while ((bytesParsed < statusLineLength) && (statusLine[bytesParsed] != '\r') && (statusLine[bytesParsed] != '\n')) {
1585 // below should be wrapped in while (response[i] != ' ') to be more robust???
1586 switch (statusState) {
1587 case BeforeVersionNumbers:
1588 if (statusLine[bytesParsed] == '/') {
1589 //INET_ASSERT(statusState == BeforeVersionNumbers);
1590 statusState++; // = MajorVersionNumber
1592 else if (statusLine[bytesParsed] == ' ') {
1593 statusState = StatusCodeNumber;
1598 case MajorVersionNumber:
1600 if (statusLine[bytesParsed] == '.') {
1601 //INET_ASSERT(statusState == MajorVersionNumber);
1602 statusState++; // = MinorVersionNumber
1606 goto case MinorVersionNumber;
1608 case MinorVersionNumber:
1610 if (statusLine[bytesParsed] == ' ') {
1611 //INET_ASSERT(statusState == MinorVersionNumber);
1612 statusState++; // = StatusCodeNumber
1616 goto case StatusCodeNumber;
1618 case StatusCodeNumber:
1620 if (Char.IsDigit((char)statusLine[bytesParsed])) {
1621 int val = statusLine[bytesParsed] - '0';
1622 statusLineInts[statusState] = statusLineInts[statusState] * 10 + val;
1624 else if (statusLineInts[StatusCodeNumber] > 0) {
1626 // we eat spaces before status code is found,
1627 // once we have the status code we can go on to the next
1628 // state on the next non-digit. This is done
1629 // to cover cases with several spaces between version
1630 // and the status code number.
1633 statusState++; // = AfterStatusCode
1636 else if (!Char.IsWhiteSpace((char) statusLine[bytesParsed])) {
1637 statusLineInts[statusState] = (int)-1;
1642 case AfterStatusCode:
1643 if (statusLine[bytesParsed] != ' ') {
1644 lastUnSpaceIndex = bytesParsed;
1645 if (startIndexStatusDescription == -1) {
1646 startIndexStatusDescription = bytesParsed;
1653 if (m_MaximumResponseHeadersLength>=0 && ++m_TotalResponseHeadersLength>=m_MaximumResponseHeadersLength) {
1654 parseStatus = DataParseStatus.DataTooBig;
1659 statusLineSize = bytesParsed;
1661 // add to Description if already partialy parsed
1662 if (startIndexStatusDescription != -1) {
1663 statusDescription +=
1664 WebHeaderCollection.HeaderEncoding.GetString(
1666 startIndexStatusDescription,
1667 lastUnSpaceIndex - startIndexStatusDescription + 1 );
1670 if (bytesParsed == statusLineLength) {
1672 // response now points one past the end of the buffer. We may be looking
1675 // if we're at the end of the connection then the server sent us an
1676 // incorrectly formatted response. Probably an error.
1678 // Otherwise its a partial response. We need more
1680 parseStatus = DataParseStatus.NeedMoreData;
1682 // if we really hit the end of the response then update the amount of
1685 GlobalLog.Leave("Connection#" + ValidationHelper.HashString(this) + "::ParseStatusLine", parseStatus.ToString());
1689 while ((bytesParsed < statusLineLength)
1690 && ((statusLine[bytesParsed] == '\r') || (statusLine[bytesParsed] == ' '))) {
1692 if (m_MaximumResponseHeadersLength>=0 && ++m_TotalResponseHeadersLength>=m_MaximumResponseHeadersLength) {
1693 parseStatus = DataParseStatus.DataTooBig;
1698 if (bytesParsed == statusLineLength) {
1701 // hit end of buffer without finding LF
1704 parseStatus = DataParseStatus.NeedMoreData;
1708 else if (statusLine[bytesParsed] == '\n') {
1710 if (m_MaximumResponseHeadersLength>=0 && ++m_TotalResponseHeadersLength>=m_MaximumResponseHeadersLength) {
1711 parseStatus = DataParseStatus.DataTooBig;
1715 // if we found the empty line then we are done
1717 parseStatus = DataParseStatus.Done;
1722 // Now we have our parsed header to add to the array
1726 if (parseStatus == DataParseStatus.Done && statusState != AfterStatusCode) {
1727 // need to handle the case where we parse the StatusCode,
1728 // but didn't get a status Line, and there was no space afer it.
1729 if (statusState != StatusCodeNumber || statusLineInts[StatusCodeNumber] <= 0) {
1731 // we're done with the status line, if we didn't parse all the
1732 // numbers needed this is invalid protocol on the server
1734 parseStatus = DataParseStatus.Invalid;
1738 GlobalLog.Print("Connection#" + ValidationHelper.HashString(this) + "::ParseStatusLine() StatusCode:" + statusLineInts[StatusCodeNumber] + " MajorVersionNumber:" + statusLineInts[MajorVersionNumber] + " MinorVersionNumber:" + statusLineInts[MinorVersionNumber] + " StatusDescription:" + ValidationHelper.ToString(statusDescription));
1739 GlobalLog.Leave("Connection#" + ValidationHelper.HashString(this) + "::ParseStatusLine", parseStatus.ToString());
1741 if (parseStatus == DataParseStatus.Invalid) {
1742 parseError.Section = WebParseErrorSection.ResponseStatusLine;
1743 parseError.Code = WebParseErrorCode.Generic;
1749 // Must all start with a different first character.
1750 private static readonly string[] s_ShortcutStatusDescriptions = new string[] { "OK", "Continue", "Unauthorized" };
1753 // Updated version of ParseStatusLine() - secure and fast
1755 private static unsafe DataParseStatus ParseStatusLineStrict(
1757 int statusLineLength,
1758 ref int bytesParsed,
1759 ref int statusState,
1760 StatusLineValues statusLineValues,
1761 int maximumHeaderLength,
1762 ref int totalBytesParsed,
1763 ref WebParseError parseError)
1765 GlobalLog.Enter("Connection::ParseStatusLineStrict", statusLineLength.ToString(NumberFormatInfo.InvariantInfo) + ", " + bytesParsed.ToString(NumberFormatInfo.InvariantInfo) + ", " + statusState.ToString(NumberFormatInfo.InvariantInfo));
1766 GlobalLog.ThreadContract(ThreadKinds.Unknown, "Connection::ParseStatusLineStrict");
1767 GlobalLog.Assert((statusLineLength - bytesParsed) >= 0, "Connection::ParseStatusLineStrict()|(statusLineLength - bytesParsed) < 0");
1768 GlobalLog.Assert(maximumHeaderLength <= 0 || totalBytesParsed <= maximumHeaderLength, "Connection::ParseStatusLineStrict()|Headers already read exceeds limit.");
1770 // Remember where we started.
1771 int initialBytesParsed = bytesParsed;
1773 // Set up parsing status with what will happen if we exceed the buffer.
1774 DataParseStatus parseStatus = DataParseStatus.DataTooBig;
1775 int effectiveMax = maximumHeaderLength <= 0 ? int.MaxValue : (maximumHeaderLength - totalBytesParsed + bytesParsed);
1776 if (statusLineLength < effectiveMax)
1778 parseStatus = DataParseStatus.NeedMoreData;
1779 effectiveMax = statusLineLength;
1783 if (bytesParsed >= effectiveMax)
1786 fixed (byte* byteBuffer = statusLine)
1788 // Use this switch to jump midway into the action. They all fall through until the end of the buffer is reached or
1789 // the status line is fully parsed.
1790 switch (statusState)
1792 case BeforeVersionNumbers:
1793 // This takes advantage of the fact that this token must be the very first thing in the response.
1794 while (totalBytesParsed - initialBytesParsed + bytesParsed < BeforeVersionNumberBytes.Length)
1796 if ((byte)BeforeVersionNumberBytes[totalBytesParsed - initialBytesParsed + bytesParsed] != byteBuffer[bytesParsed])
1798 parseStatus = DataParseStatus.Invalid;
1802 if(++bytesParsed == effectiveMax)
1806 // When entering the MajorVersionNumber phase, make sure at least one digit is present.
1807 if (byteBuffer[bytesParsed] == '.')
1809 parseStatus = DataParseStatus.Invalid;
1813 statusState = MajorVersionNumber;
1814 goto case MajorVersionNumber;
1816 case MajorVersionNumber:
1817 while (byteBuffer[bytesParsed] != '.')
1819 if (byteBuffer[bytesParsed] < '0' || byteBuffer[bytesParsed] > '9')
1821 parseStatus = DataParseStatus.Invalid;
1825 statusLineValues.MajorVersion = statusLineValues.MajorVersion * 10 + byteBuffer[bytesParsed] - '0';
1827 if (++bytesParsed == effectiveMax)
1831 // Need visibility past the dot.
1832 if (bytesParsed + 1 == effectiveMax)
1836 // When entering the MinorVersionNumber phase, make sure at least one digit is present.
1837 if (byteBuffer[bytesParsed] == ' ')
1839 parseStatus = DataParseStatus.Invalid;
1843 statusState = MinorVersionNumber;
1844 goto case MinorVersionNumber;
1846 case MinorVersionNumber:
1847 // Only a single SP character is allowed to delimit fields in the status line.
1848 while (byteBuffer[bytesParsed] != ' ')
1850 if (byteBuffer[bytesParsed] < '0' || byteBuffer[bytesParsed] > '9')
1852 parseStatus = DataParseStatus.Invalid;
1856 statusLineValues.MinorVersion = statusLineValues.MinorVersion * 10 + byteBuffer[bytesParsed] - '0';
1858 if (++bytesParsed == effectiveMax)
1862 statusState = StatusCodeNumber;
1864 // Start the status code out as "1". This will effectively add 1000 to the code. It's used to count
1865 // the number of digits to make sure it's three. At the end, subtract 1000.
1866 statusLineValues.StatusCode = 1;
1868 // Move past the space.
1869 if (++bytesParsed == effectiveMax)
1872 goto case StatusCodeNumber;
1874 case StatusCodeNumber:
1875 // RFC2616 says codes with an unrecognized first digit
1876 // should be rejected. We're allowing the application to define their own "understanding" of
1877 // 0, 6, 7, 8, and 9xx codes.
1878 while (byteBuffer[bytesParsed] >= '0' && byteBuffer[bytesParsed] <= '9')
1880 // Make sure it isn't too big. The leading '1' will be removed after three digits are read.
1881 if (statusLineValues.StatusCode >= 1000)
1883 parseStatus = DataParseStatus.Invalid;
1887 statusLineValues.StatusCode = statusLineValues.StatusCode * 10 + byteBuffer[bytesParsed] - '0';
1889 if (++bytesParsed == effectiveMax)
1893 // Make sure there was enough, and exactly one space.
1894 if (byteBuffer[bytesParsed] != ' ' || statusLineValues.StatusCode < 1000)
1896 if(byteBuffer[bytesParsed] == '\r' && statusLineValues.StatusCode >= 1000){
1897 statusLineValues.StatusCode -= 1000;
1898 statusState = AfterCarriageReturn;
1899 if (++bytesParsed == effectiveMax)
1901 goto case AfterCarriageReturn;
1903 parseStatus = DataParseStatus.Invalid;
1907 // Remove the extra leading 1.
1908 statusLineValues.StatusCode -= 1000;
1910 statusState = AfterStatusCode;
1912 // Move past the space.
1913 if (++bytesParsed == effectiveMax)
1916 goto case AfterStatusCode;
1918 case AfterStatusCode:
1920 // Check for shortcuts.
1921 if (statusLineValues.StatusDescription == null)
1923 foreach (string s in s_ShortcutStatusDescriptions)
1925 if (bytesParsed < effectiveMax - s.Length && byteBuffer[bytesParsed] == (byte) s[0])
1928 byte *pBuffer = byteBuffer + bytesParsed + 1;
1929 for(i = 1; i < s.Length; i++)
1930 if (*(pBuffer++) != (byte) s[i])
1934 statusLineValues.StatusDescription = s;
1935 bytesParsed += s.Length;
1942 int beginning = bytesParsed;
1944 while (byteBuffer[bytesParsed] != '\r')
1946 if (byteBuffer[bytesParsed] < ' ' || byteBuffer[bytesParsed] == 127)
1948 parseStatus = DataParseStatus.Invalid;
1952 if (++bytesParsed == effectiveMax)
1954 string s = WebHeaderCollection.HeaderEncoding.GetString(byteBuffer + beginning, bytesParsed - beginning);
1955 if (statusLineValues.StatusDescription == null)
1956 statusLineValues.StatusDescription = s;
1958 statusLineValues.StatusDescription += s;
1964 if (bytesParsed > beginning)
1966 string s = WebHeaderCollection.HeaderEncoding.GetString(byteBuffer + beginning, bytesParsed - beginning);
1967 if (statusLineValues.StatusDescription == null)
1968 statusLineValues.StatusDescription = s;
1970 statusLineValues.StatusDescription += s;
1972 else if (statusLineValues.StatusDescription == null)
1974 statusLineValues.StatusDescription = "";
1977 statusState = AfterCarriageReturn;
1979 // Move past the CR.
1980 if (++bytesParsed == effectiveMax)
1983 goto case AfterCarriageReturn;
1986 case AfterCarriageReturn:
1987 if (byteBuffer[bytesParsed] != '\n')
1989 parseStatus = DataParseStatus.Invalid;
1993 parseStatus = DataParseStatus.Done;
2000 totalBytesParsed += bytesParsed - initialBytesParsed;
2002 GlobalLog.Print("Connection::ParseStatusLineStrict() StatusCode:" + statusLineValues.StatusCode + " MajorVersionNumber:" + statusLineValues.MajorVersion + " MinorVersionNumber:" + statusLineValues.MinorVersion + " StatusDescription:" + ValidationHelper.ToString(statusLineValues.StatusDescription));
2003 GlobalLog.Leave("Connection::ParseStatusLineStrict", parseStatus.ToString());
2005 if (parseStatus == DataParseStatus.Invalid) {
2006 parseError.Section = WebParseErrorSection.ResponseStatusLine;
2007 parseError.Code = WebParseErrorCode.Generic;
2016 Routine Description:
2018 SetStatusLineParsed - processes the result of status line,
2019 after it has been parsed, reads vars and formats result of parsing
2023 None - uses member vars
2031 private void SetStatusLineParsed() {
2032 // transfer this to response data
2033 m_ResponseData.m_StatusCode = (HttpStatusCode) m_StatusLineValues.StatusCode;
2034 m_ResponseData.m_StatusDescription = m_StatusLineValues.StatusDescription;
2035 m_ResponseData.m_IsVersionHttp11 = m_StatusLineValues.MajorVersion >= 1 && m_StatusLineValues.MinorVersion >= 1;
2036 if (ServicePoint.HttpBehaviour==HttpBehaviour.Unknown || ServicePoint.HttpBehaviour==HttpBehaviour.HTTP11 && !m_ResponseData.m_IsVersionHttp11) {
2037 // it's only safe to start doing HTTP/1.1 behaviour if the server's version was unknown
2038 // or if we need to downgrade
2039 ServicePoint.HttpBehaviour = m_ResponseData.m_IsVersionHttp11 ? HttpBehaviour.HTTP11 : HttpBehaviour.HTTP10;
2042 m_CanPipeline = ServicePoint.SupportsPipelining;
2047 ProcessHeaderData - Pulls out Content-length, and other critical
2048 data from the newly parsed headers
2056 long - size of contentLength that we are to use
2059 private long ProcessHeaderData(ref bool fHaveChunked, HttpWebRequest request, out bool dummyResponseStream)
2061 long contentLength = -1;
2062 fHaveChunked = false;
2064 // Check for the "Transfer-Encoding" header to contain the "chunked" string
2066 string transferEncodingString = m_ResponseData.m_ResponseHeaders[HttpKnownHeaderNames.TransferEncoding];
2067 if (transferEncodingString!=null) {
2068 transferEncodingString = transferEncodingString.ToLower(CultureInfo.InvariantCulture);
2069 fHaveChunked = transferEncodingString.IndexOf(HttpWebRequest.ChunkedHeader) != -1;
2072 if (!fHaveChunked) {
2074 // If the response is not chunked, parse the "Content-Length" into a long for data size.
2076 string contentLengthString = m_ResponseData.m_ResponseHeaders.ContentLength;
2077 if (contentLengthString!=null) {
2078 int index = contentLengthString.IndexOf(':');
2080 contentLengthString = contentLengthString.Substring(index + 1);
2082 bool success = long.TryParse(contentLengthString, NumberStyles.None, CultureInfo.InvariantCulture.NumberFormat, out contentLength);
2085 // in some very rare cases, a proxy server may
2086 // send us a pair of numbers in comma delimated
2087 // fashion, so we need to handle this case
2088 index = contentLengthString.LastIndexOf(',');
2090 contentLengthString = contentLengthString.Substring(index + 1);
2091 success = long.TryParse(contentLengthString, NumberStyles.None, CultureInfo.InvariantCulture.NumberFormat, out contentLength);
2097 if (contentLength < 0)
2099 GlobalLog.Leave("Connection#" + ValidationHelper.HashString(this) + "::ProcessHeaderData - ContentLength value in header: " + contentLengthString + ", HttpWebRequest#"+ValidationHelper.HashString(m_CurrentRequest));
2100 contentLength = c_InvalidContentLength; // This will indicate a CL error to the caller
2105 // ** else ** signal no content-length present??? or error out?
2106 GlobalLog.Print("Connection#" + ValidationHelper.HashString(this) + "::ProcessHeaderData() Content-Length parsed:" + contentLength.ToString(NumberFormatInfo.InvariantInfo));
2108 dummyResponseStream = !request.CanGetResponseStream || m_ResponseData.m_StatusCode < HttpStatusCode.OK ||
2109 m_ResponseData.m_StatusCode == HttpStatusCode.NoContent || (m_ResponseData.m_StatusCode == HttpStatusCode.NotModified && contentLength < 0) ;
2114 // Deciding on KEEP ALIVE
2116 bool resetKeepAlive = false;
2118 //(1) if no content-length and no chunked, then turn off keep-alive
2119 // In some cases, though, Content-Length should be assumed to be 0 based on HTTP RFC 2616
2120 if (!dummyResponseStream && contentLength < 0 && !fHaveChunked)
2122 resetKeepAlive = true;
2124 //(2) A workaround for a failed client ssl session on IIS6
2125 // The problem is that we cannot change the connection group name after it gets created.
2126 // IIS6 does not close the connection on 403 so all subsequent requests will fail to be authorized on THAT connection.
2127 //-----------------------------------------------------------------------------------------------
2130 //The DTS Issue 595216 claims that we are unnecessarily closing the
2131 //connection on 403 - even if it is a non SSL request. It seems
2132 //that the original intention is to close the request for SSL requests
2133 //The following code change would enforce closing onl fo SSL requests.
2134 //-----------------------------------------------------------------------------------------------
2135 else if (m_ResponseData.m_StatusCode == HttpStatusCode.Forbidden && NetworkStream is TlsStream)
2137 resetKeepAlive = true;
2139 // (3) Possibly cease posting a big body on the connection, was invented mainly for the very first 401 response
2141 // This optimization is for the discovery legs only. For ntlm this is fine, because the 1st actual authleg
2142 // is always sent w/ content-length = 0.
2143 // For Kerberos preauth, it there could be 1 or 2 auth legs, but we don't know how many there are in advance,
2144 // so we don't have a way of eliminating the 1st auth leg.
2145 else if (m_ResponseData.m_StatusCode > HttpWebRequest.MaxOkStatus &&
2146 ((request.CurrentMethod == KnownHttpVerb.Post || request.CurrentMethod == KnownHttpVerb.Put) &&
2147 m_MaximumUnauthorizedUploadLength >= 0 && request.ContentLength > m_MaximumUnauthorizedUploadLength
2148 && (request.CurrentAuthenticationState == null || request.CurrentAuthenticationState.Module == null)))
2150 resetKeepAlive = true;
2152 //(4) for Http/1.0 servers, we can't be sure what their behavior
2153 // in this case, so the best thing is to disable KeepAlive unless explicitly set
2159 //in v2.0, in case of SSL Requests through proxy that require NTLM authentication,
2160 //we are not honoring the Proxy-Connection: Keep-Alive header and
2161 //closing the connection.
2163 //In v1.1 we did not have this issue because in v1.1, we would have set an
2164 //EmptyProxy on the CONNECT request which kind of made it look like the
2165 //service point is a proxy service point
2167 //In v2.0, we don't use the GlobalProxySelection.GetEmptyWebProxy we use null
2168 //to indicate we are not using a proxy.
2169 //The CONNECT request is a proxy request and the service point is to the
2173 //This is a surgical fix. The "UsesProxySemantics is defined as
2174 //ServicePoint is a Proxy Service point && (scheme is != https || the request is a tunnel request)
2175 //Ideally we use one definition of whether we are going trough a proxy or not.
2176 //The fact is that if you are connecting to a proxy, it is a proxy request and
2177 //you should honor the Proxy-Connection header.
2179 //For the purpose of this QFE, when we receive a header we test
2180 //if this is a Proxy Service Point OR if this is a TUNNEL request
2184 bool haveClose = false;
2185 bool haveKeepAlive = false;
2186 string connection = m_ResponseData.m_ResponseHeaders[HttpKnownHeaderNames.Connection];
2187 if (connection == null && (
2188 (ServicePoint.InternalProxyServicePoint) ||
2189 (request.IsTunnelRequest)))
2191 connection = m_ResponseData.m_ResponseHeaders[HttpKnownHeaderNames.ProxyConnection];
2194 if (connection != null) {
2195 connection = connection.ToLower(CultureInfo.InvariantCulture);
2196 if (connection.IndexOf("keep-alive") != -1) {
2197 haveKeepAlive = true;
2199 else if (connection.IndexOf("close") != -1) {
2204 if ((haveClose && ServicePoint.HttpBehaviour==HttpBehaviour.HTTP11) ||
2205 (!haveKeepAlive && ServicePoint.HttpBehaviour<=HttpBehaviour.HTTP10))
2207 resetKeepAlive = true;
2215 m_KeepAlive = false;
2221 return contentLength;
2224 internal bool KeepAlive
2232 internal bool NonKeepAliveRequestPipelined
2236 return m_NonKeepAliveRequestPipelined;
2244 Handles parsing of the blocks of data received after buffer,
2245 distributes the data to stream constructors as needed
2247 returnResult - contains a object containing Requests
2248 that must be notified upon return from callback
2251 private DataParseStatus ParseStreamData(ref ConnectionReturnResult returnResult)
2253 GlobalLog.Enter("Connection#" + ValidationHelper.HashString(this) + "::ParseStreamData");
2255 if (m_CurrentRequest == null)
2257 GlobalLog.Leave("Connection#" + ValidationHelper.HashString(this) + "::ParseStreamData - Aborted Request, return DataParseStatus.Invalid");
2258 m_ParseError.Section = WebParseErrorSection.Generic;
2259 m_ParseError.Code = WebParseErrorCode.UnexpectedServerResponse;
2260 return DataParseStatus.Invalid;
2263 bool fHaveChunked = false;
2264 bool dummyResponseStream;
2265 // content-length if there is one
2266 long contentLength = ProcessHeaderData(ref fHaveChunked, m_CurrentRequest, out dummyResponseStream);
2268 GlobalLog.Assert(!fHaveChunked || contentLength == -1, "Connection#{0}::ParseStreamData()|fHaveChunked but contentLength != -1", ValidationHelper.HashString(this));
2270 if (contentLength == c_InvalidContentLength)
2272 m_ParseError.Section = WebParseErrorSection.ResponseHeader;
2273 m_ParseError.Code = WebParseErrorCode.InvalidContentLength;
2274 return DataParseStatus.Invalid;
2277 // bytes left over that have not been parsed
2278 int bufferLeft = (m_BytesRead - m_BytesScanned);
2280 if (m_ResponseData.m_StatusCode > HttpWebRequest.MaxOkStatus)
2282 // This will tell the request to be prepared for possible connection drop
2283 // Also that will stop writing on the wire if the connection is not kept alive
2284 m_CurrentRequest.ErrorStatusCodeNotify(this, m_KeepAlive, false);
2289 // If pipelining, then look for extra data that could
2290 // be part of of another stream, if its there,
2291 // then we need to copy it, add it to a stream,
2292 // and then continue with the next headers
2295 if (dummyResponseStream)
2298 fHaveChunked = false;
2304 if (!fHaveChunked && (contentLength <= (long)Int32.MaxValue))
2306 bytesToCopy = (int)contentLength;
2310 DataParseStatus result;
2312 GlobalLog.Print("Connection#" + ValidationHelper.HashString(this) + "::ParseStreamData() bytesToCopy:" + bytesToCopy + " bufferLeft:" + bufferLeft);
2314 if (m_CurrentRequest.IsWebSocketRequest && m_ResponseData.m_StatusCode == HttpStatusCode.SwitchingProtocols)
2316 m_ResponseData.m_ConnectStream = new ConnectStream(this, m_ReadBuffer, m_BytesScanned, bufferLeft, bufferLeft, fHaveChunked, m_CurrentRequest);
2318 // The parsing will be resumed from m_BytesScanned when response stream is closed.
2319 result = DataParseStatus.Done;
2322 else if (bytesToCopy != -1 && bytesToCopy <= bufferLeft)
2324 m_ResponseData.m_ConnectStream = new ConnectStream(this, m_ReadBuffer, m_BytesScanned, bytesToCopy, dummyResponseStream? 0: contentLength, fHaveChunked, m_CurrentRequest);
2326 // The parsing will be resumed from m_BytesScanned when response stream is closed.
2327 result = DataParseStatus.ContinueParsing;
2328 m_BytesScanned += bytesToCopy;
2332 m_ResponseData.m_ConnectStream = new ConnectStream(this, m_ReadBuffer, m_BytesScanned, bufferLeft, dummyResponseStream? 0: contentLength, fHaveChunked, m_CurrentRequest);
2334 // This is the default case where we have a buffer with no more streams except the last one to create so we create it.
2335 // Note the buffer is fully consumed so we can reset the buffer offests.
2336 result = DataParseStatus.Done;
2340 m_ResponseData.m_ContentLength = contentLength;
2341 ConnectionReturnResult.Add(ref returnResult, m_CurrentRequest, m_ResponseData.Clone());
2344 GlobalLog.DebugUpdateRequest(m_CurrentRequest, this, GlobalLog.WaitingForReadDoneFlag);
2347 GlobalLog.Leave("Connection#" + ValidationHelper.HashString(this) + "::ParseStreamData");
2348 return result; // response stream is taking over the reading
2351 // Called before restarting Read operations
2352 private void ClearReaderState() {
2359 ParseResponseData - Parses the incomming headers, and handles
2360 creation of new streams that are found while parsing, and passes
2361 extra data the new streams
2365 returnResult - returns an object containing items that need to be called
2366 at the end of the read callback
2370 bool - true if one should continue reading more data
2373 private DataParseStatus ParseResponseData(ref ConnectionReturnResult returnResult, out bool requestDone, out CoreResponseData continueResponseData)
2375 GlobalLog.Enter("Connection#" + ValidationHelper.HashString(this) + "::ParseResponseData()");
2377 DataParseStatus parseStatus = DataParseStatus.NeedMoreData;
2378 DataParseStatus parseSubStatus;
2380 // Indicates whether or not at least one whole request was processed in this loop.
2381 // (i.e. Whether ParseStreamData() was called.
2382 requestDone = false;
2383 continueResponseData = null;
2385 // loop in case of multiple sets of headers or streams,
2386 // that may be generated due to a pipelined response
2388 // Invariants: at the start of this loop, m_BytesRead
2389 // is the number of bytes in the buffer, and m_BytesScanned
2390 // is how many bytes of the buffer we've consumed so far.
2391 // and the m_ReadState var will be updated at end of
2392 // each code path, call to this function to reflect,
2393 // the state, or error condition of the parsing of data
2395 // We use the following variables in the code below:
2397 // m_ReadState - tracks the current state of our Parsing in a
2399 // Start - initial start state and begining of response
2400 // StatusLine - the first line sent in response, include status code
2401 // Headers - \r\n delimiated Header parsing until we find entity body
2402 // Data - Entity Body parsing, if we have all data, we create stream directly
2404 // m_ResponseData - An object used to gather Stream, Headers, and other
2405 // tidbits so that a request/Response can receive this data when
2406 // this code is finished processing
2408 // m_ReadBuffer - Of course the buffer of data we are parsing from
2410 // m_BytesScanned - The bytes scanned in this buffer so far,
2411 // since its always assumed that parse to completion, this
2412 // var becomes ended of known data at the end of this function,
2415 // m_BytesRead - The total bytes read in buffer, should be const,
2416 // till its updated at end of function.
2420 // Now attempt to parse the data,
2421 // we first parse status line,
2422 // then read headers,
2423 // and finally transfer results to a new stream, and tell request
2426 switch (m_ReadState) {
2428 case ReadState.Start:
2431 if (m_CurrentRequest == null)
2435 if (m_WriteList.Count == 0 || ((m_CurrentRequest = m_WriteList[0] as HttpWebRequest) == null))
2437 m_ParseError.Section = WebParseErrorSection.Generic;
2438 m_ParseError.Code = WebParseErrorCode.Generic;
2439 parseStatus = DataParseStatus.Invalid;
2446 // Start of new response. Transfer the keep-alive context from the corresponding request to
2449 m_KeepAlive &= (m_CurrentRequest.KeepAlive || m_CurrentRequest.NtlmKeepAlive);
2451 m_MaximumResponseHeadersLength = m_CurrentRequest.MaximumResponseHeadersLength * 1024;
2452 m_ResponseData = new CoreResponseData();
2453 m_ReadState = ReadState.StatusLine;
2454 m_TotalResponseHeadersLength = 0;
2456 InitializeParseStatusLine();
2457 goto case ReadState.StatusLine;
2459 case ReadState.StatusLine:
2461 // Reads HTTP status response line
2463 if (SettingsSectionInternal.Section.UseUnsafeHeaderParsing)
2465 // This one uses an array to store the parsed values in. Marshal between this legacy way.
2466 int[] statusInts = new int[] { 0, m_StatusLineValues.MajorVersion, m_StatusLineValues.MinorVersion, m_StatusLineValues.StatusCode };
2467 if (m_StatusLineValues.StatusDescription == null)
2468 m_StatusLineValues.StatusDescription = "";
2470 parseSubStatus = ParseStatusLine(
2471 m_ReadBuffer, // buffer we're working with
2472 m_BytesRead, // total bytes read so far
2473 ref m_BytesScanned, // index off of what we've scanned
2475 ref m_StatusLineValues.StatusDescription,
2479 m_StatusLineValues.MajorVersion = statusInts[1];
2480 m_StatusLineValues.MinorVersion = statusInts[2];
2481 m_StatusLineValues.StatusCode = statusInts[3];
2485 parseSubStatus = ParseStatusLineStrict(
2486 m_ReadBuffer, // buffer we're working with
2487 m_BytesRead, // total bytes read so far
2488 ref m_BytesScanned, // index off of what we've scanned
2491 m_MaximumResponseHeadersLength,
2492 ref m_TotalResponseHeadersLength,
2496 if (parseSubStatus == DataParseStatus.Done)
2498 if (Logging.On) Logging.PrintInfo(Logging.Web, this, SR.GetString(SR.net_log_received_status_line, m_StatusLineValues.MajorVersion+"."+m_StatusLineValues.MinorVersion, m_StatusLineValues.StatusCode, m_StatusLineValues.StatusDescription));
2499 SetStatusLineParsed();
2500 m_ReadState = ReadState.Headers;
2501 m_ResponseData.m_ResponseHeaders = new WebHeaderCollection(WebHeaderCollectionType.HttpWebResponse);
2502 goto case ReadState.Headers;
2504 else if (parseSubStatus != DataParseStatus.NeedMoreData)
2507 // report error - either Invalid or DataTooBig
2509 GlobalLog.Print("Connection#" + ValidationHelper.HashString(this) + "::ParseResponseData() ParseStatusLine() parseSubStatus:" + parseSubStatus.ToString());
2510 parseStatus = parseSubStatus;
2514 break; // read more data
2516 case ReadState.Headers:
2518 // Parse additional lines of header-name: value pairs
2520 if (m_BytesScanned >= m_BytesRead) {
2522 // we already can tell we need more data
2527 if (SettingsSectionInternal.Section.UseUnsafeHeaderParsing)
2529 parseSubStatus = m_ResponseData.m_ResponseHeaders.ParseHeaders(
2533 ref m_TotalResponseHeadersLength,
2534 m_MaximumResponseHeadersLength,
2539 parseSubStatus = m_ResponseData.m_ResponseHeaders.ParseHeadersStrict(
2543 ref m_TotalResponseHeadersLength,
2544 m_MaximumResponseHeadersLength,
2548 if (parseSubStatus == DataParseStatus.Invalid || parseSubStatus == DataParseStatus.DataTooBig)
2553 GlobalLog.Print("Connection#" + ValidationHelper.HashString(this) + "::ParseResponseData() ParseHeaders() parseSubStatus:" + parseSubStatus.ToString());
2554 parseStatus = parseSubStatus;
2557 else if (parseSubStatus == DataParseStatus.Done)
2559 if (Logging.On) Logging.PrintInfo(Logging.Web, this, SR.GetString(SR.net_log_received_headers, m_ResponseData.m_ResponseHeaders.ToString(true)));
2561 GlobalLog.Print("Connection#" + ValidationHelper.HashString(this) + "::ParseResponseData() DataParseStatus.Done StatusCode:" + (int)m_ResponseData.m_StatusCode + " m_BytesRead:" + m_BytesRead + " m_BytesScanned:" + m_BytesScanned);
2563 //get the IIS server version
2564 if(m_IISVersion == -1){
2565 string server = m_ResponseData.m_ResponseHeaders.Server;
2566 if (server != null && server.ToLower(CultureInfo.InvariantCulture).Contains("microsoft-iis")){
2567 int i = server.IndexOf("/");
2568 if(i++>0 && i <server.Length){
2569 m_IISVersion = server[i++] - '0';
2570 while(i < server.Length && Char.IsDigit(server[i])) {
2571 m_IISVersion = m_IISVersion*10 + server[i++] - '0';
2575 //we got a response,so if we don't know the server by now and it wasn't a 100 continue,
2576 //we can't assume we will ever know it. IIS5 sends its server header w/ the continue
2578 if(m_IISVersion == -1 && m_ResponseData.m_StatusCode != HttpStatusCode.Continue){
2583 if (m_ResponseData.m_StatusCode == HttpStatusCode.Continue || m_ResponseData.m_StatusCode == HttpStatusCode.BadRequest) {
2585 GlobalLog.Assert(m_CurrentRequest != null, "Connection#{0}::ParseResponseData()|m_CurrentRequest == null", ValidationHelper.HashString(this));
2586 GlobalLog.Print("Connection#" + ValidationHelper.HashString(this) + "::ParseResponseData() HttpWebRequest#" + ValidationHelper.HashString(m_CurrentRequest));
2588 if (m_ResponseData.m_StatusCode == HttpStatusCode.BadRequest) {
2589 // If we have a 400 and we were sending a chunked request going through to a proxy with a chunked upload,
2590 // this proxy is a partially compliant so shut off fancy features (pipelining and chunked uploads)
2591 GlobalLog.Print("Connection#" + ValidationHelper.HashString(this) + "::ParseResponseData() got a 400 StatusDescription:" + m_ResponseData.m_StatusDescription);
2592 if (ServicePoint.HttpBehaviour == HttpBehaviour.HTTP11
2593 && m_CurrentRequest.HttpWriteMode==HttpWriteMode.Chunked
2594 && m_ResponseData.m_ResponseHeaders.Via != null
2595 && string.Compare(m_ResponseData.m_StatusDescription, "Bad Request ( The HTTP request includes a non-supported header. Contact the Server administrator. )", StringComparison.OrdinalIgnoreCase)==0) {
2596 GlobalLog.Print("Connection#" + ValidationHelper.HashString(this) + "::ParseResponseData() downgrading server to HTTP11PartiallyCompliant.");
2597 ServicePoint.HttpBehaviour = HttpBehaviour.HTTP11PartiallyCompliant;
2601 // If we have an HTTP continue, eat these headers and look
2604 // we got a 100 Continue. set this on the HttpWebRequest
2606 m_CurrentRequest.Saw100Continue = true;
2607 if (!ServicePoint.Understands100Continue) {
2609 // and start expecting it again if this behaviour was turned off
2611 GlobalLog.Print("Connection#" + ValidationHelper.HashString(this) + "::ParseResponseData() HttpWebRequest#" + ValidationHelper.HashString(m_CurrentRequest) + " ServicePoint#" + ValidationHelper.HashString(ServicePoint) + " sent UNexpected 100 Continue");
2612 ServicePoint.Understands100Continue = true;
2616 // set Continue Ack on request.
2618 GlobalLog.Print("Connection#" + ValidationHelper.HashString(this) + "::ParseResponseData() calling SetRequestContinue()");
2619 continueResponseData = m_ResponseData;
2621 //if we got a 100continue we ---- it and start looking for a final response
2622 goto case ReadState.Start;
2626 m_ReadState = ReadState.Data;
2627 goto case ReadState.Data;
2633 case ReadState.Data:
2635 // (check status code for continue handling)
2636 // 1. Figure out if its Chunked, content-length, or encoded
2637 // 2. Takes extra data, place in stream(s)
2638 // 3. Wake up blocked stream requests
2641 // Got through one entire response
2644 // parse and create a stream if needed
2645 parseStatus = ParseStreamData(ref returnResult);
2647 GlobalLog.Print("Connection#" + ValidationHelper.HashString(this) + "::ParseResponseData() result:" + parseStatus);
2648 GlobalLog.Print("Connection#" + ValidationHelper.HashString(this) + "::ParseResponseData()" + " WriteDone:" + m_WriteDone + " ReadDone:" + m_ReadDone + " WaitList:" + m_WaitList.Count + " WriteList:" + m_WriteList.Count);
2652 if (m_BytesScanned == m_BytesRead)
2655 GlobalLog.Print("Connection#" + ValidationHelper.HashString(this) + "::ParseResponseData() m_ReadState:" + m_ReadState);
2656 GlobalLog.Leave("Connection#" + ValidationHelper.HashString(this) + "::ParseResponseData()", parseStatus.ToString());
2662 /// Cause the Connection to Close and Abort its socket,
2663 /// after the next request is completed. If the Connection
2664 /// is already idle, then Aborts the socket immediately.
2667 internal void CloseOnIdle() {
2668 // The timer thread is allowed to call this. (It doesn't call user code and doesn't block.)
2669 GlobalLog.ThreadContract(ThreadKinds.Unknown, ThreadKinds.SafeSources | ThreadKinds.Timer, "Connection#" + ValidationHelper.HashString(this) + "::CloseOnIdle");
2672 m_KeepAlive = false;
2673 m_RemovedFromConnectionList = true;
2681 GC.SuppressFinalize(this);
2686 internal bool AbortOrDisassociate(HttpWebRequest request, WebException webException)
2688 GlobalLog.Enter("Connection#" + ValidationHelper.HashString(this) + "::AbortOrDisassociate", "request#" + ValidationHelper.HashString(request));
2689 GlobalLog.ThreadContract(ThreadKinds.Unknown, "Connection#" + ValidationHelper.HashString(this) + "::AbortOrDisassociate()");
2691 ConnectionReturnResult result = null;
2694 int idx = m_WriteList.IndexOf(request);
2695 // If the request is in the submission AND this is the first request we have to abort the connection,
2696 // Otheriwse we simply disassociate it from the current connection.
2699 WaitListItem foundItem = null;
2701 if (m_WaitList.Count > 0)
2703 foundItem = m_WaitList.Find(o => object.ReferenceEquals(o.Request, request));
2706 // If not found then the request must be already dispatched and the response stream is drained
2707 // If so then we let request.Abort() to deal with this situation.
2708 if (foundItem != null)
2710 NetworkingPerfCounters.Instance.IncrementAverage(NetworkingPerfCounterName.HttpWebRequestAvgQueueTime,
2711 foundItem.QueueStartTime);
2712 m_WaitList.Remove(foundItem);
2713 UnlockIfNeeded(foundItem.Request);
2716 GlobalLog.Leave("Connection#" + ValidationHelper.HashString(this) + "::AbortOrDisassociate()", "Request was wisassociated");
2721 // Make this connection Keep-Alive=false, remove the request and do not close the connection
2722 // When the active request completes, the rest of the pipeline (minus aborted request) will be resubmitted.
2723 m_WriteList.RemoveAt(idx);
2724 m_KeepAlive = false;
2725 GlobalLog.Leave("Connection#" + ValidationHelper.HashString(this) + "::AbortOrDisassociate()", "Request was Disassociated from the Write List, idx = " + idx);
2733 m_KeepAlive = false;
2734 if (webException != null && m_InnerException == null)
2736 m_InnerException = webException;
2737 m_Error = webException.Status;
2741 m_Error = WebExceptionStatus.RequestCanceled;
2744 PrepareCloseConnectionSocket(ref result);
2745 // Hard Close the socket.
2747 FreeReadBuffer(); // Do it after close completes to insure buffer not in use
2750 catch (Exception exception)
2752 t_LastStressException = exception;
2753 if (!NclUtilities.IsFatal(exception)){
2754 GlobalLog.Assert("Connection#" + ValidationHelper.HashString(this) + "::AbortOrDisassociate()", exception.Message);
2759 ConnectionReturnResult.SetResponses(result);
2760 GlobalLog.Leave("Connection#" + ValidationHelper.HashString(this) + "::AbortOrDisassociate()", "Connection Aborted");
2766 private static Exception t_LastStressException;
2769 internal void AbortSocket(bool isAbortState)
2771 // The timer/finalization thread is allowed to call this. (It doesn't call user code and doesn't block.)
2772 GlobalLog.ThreadContract(ThreadKinds.Unknown, ThreadKinds.SafeSources | ThreadKinds.Timer | ThreadKinds.Finalization, "Connection#" + ValidationHelper.HashString(this) + "::AbortSocket");
2773 GlobalLog.Enter("Connection#" + ValidationHelper.HashString(this) + "::Abort", "isAbortState:" + isAbortState.ToString());
2780 // This one is recoverable, set it to keep Read/Write StartNextRequest happy.
2781 m_Error = WebExceptionStatus.KeepAliveFailure;
2784 // Stream close isn't threadsafe.
2788 FreeReadBuffer(); // Do it after close completes to insure buffer not in use
2791 GlobalLog.Leave("Connection#" + ValidationHelper.HashString(this) + "::Abort", "isAbortState:" + isAbortState.ToString());
2797 PrepareCloseConnectionSocket - reset the connection requests list.
2799 This method is called when we want to close the conection.
2800 It must be called with the critical section held.
2801 The caller must call this.Close if decided to call this method.
2803 All connection closes (either ours or server initiated) eventually go through here.
2805 As to what we do: we loop through our write and wait list and pull requests
2806 off it, and give each request an error failure. Then the caller will
2807 dispatch the responses.
2811 private void PrepareCloseConnectionSocket(ref ConnectionReturnResult returnResult)
2813 GlobalLog.Enter("Connection#" + ValidationHelper.HashString(this) + "::PrepareCloseConnectionSocket", m_Error.ToString());
2815 // Effectivelly, closing a connection makes it exempted from the "Idling" logic
2816 m_IdleSinceUtc = DateTime.MinValue;
2817 CanBePooled = false;
2819 if (m_WriteList.Count != 0 || m_WaitList.Count != 0)
2822 GlobalLog.Print("Connection#" + ValidationHelper.HashString(this) + "::PrepareCloseConnectionSocket() m_WriteList.Count:" + m_WriteList.Count);
2823 DebugDumpWriteListEntries();
2824 GlobalLog.Print("Connection#" + ValidationHelper.HashString(this) + "::PrepareCloseConnectionSocket() m_WaitList.Count:" + m_WaitList.Count);
2825 DebugDumpWaitListEntries();
2827 HttpWebRequest lockedRequest = LockedRequest;
2829 if (lockedRequest != null)
2831 bool callUnlockRequest = false;
2832 GlobalLog.Print("Connection#" + ValidationHelper.HashString(this) + "::PrepareCloseConnectionSocket() looking for HttpWebRequest#" + ValidationHelper.HashString(lockedRequest));
2834 foreach (HttpWebRequest request in m_WriteList)
2836 if (request == lockedRequest) {
2837 callUnlockRequest = true;
2841 if (!callUnlockRequest) {
2842 foreach (WaitListItem item in m_WaitList) {
2843 if (item.Request == lockedRequest) {
2844 callUnlockRequest = true;
2849 if (callUnlockRequest) {
2854 HttpWebRequest[] requestArray = null;
2856 // WaitList gets Isolated exception status, free to retry multiple times
2857 if (m_WaitList.Count != 0)
2859 requestArray = new HttpWebRequest[m_WaitList.Count];
2860 for (int i = 0; i < m_WaitList.Count; i++)
2862 requestArray[i] = m_WaitList[i].Request;
2864 ConnectionReturnResult.AddExceptionRange(ref returnResult, requestArray, ExceptionHelper.IsolatedException);
2868 // WriteList (except for single request list) gets Recoverable exception status, may be retired if not failed once
2869 // For a single request list the exception is computed here
2870 // InnerExeption if any may tell more details in both cases
2872 if (m_WriteList.Count != 0)
2874 Exception theException = m_InnerException;
2877 if(theException != null)
2878 GlobalLog.Print(theException.ToString());
2880 GlobalLog.Print("m_Error = "+ m_Error.ToString());
2882 if (!(theException is WebException) && !(theException is SecurityException))
2884 if (m_Error == WebExceptionStatus.ServerProtocolViolation)
2886 string errorString = NetRes.GetWebStatusString(m_Error);
2888 string detailedInfo = "";
2889 if (m_ParseError.Section != WebParseErrorSection.Generic)
2890 detailedInfo += " Section=" + m_ParseError.Section.ToString();
2891 if (m_ParseError.Code != WebParseErrorCode.Generic) {
2892 detailedInfo += " Detail=" + SR.GetString("net_WebResponseParseError_" + m_ParseError.Code.ToString());
2894 if (detailedInfo.Length != 0)
2895 errorString += "." + detailedInfo;
2897 theException = new WebException(errorString,
2901 WebExceptionInternalStatus.RequestFatal);
2903 else if (m_Error == WebExceptionStatus.SecureChannelFailure)
2905 theException = new WebException(NetRes.GetWebStatusString("net_requestaborted", WebExceptionStatus.SecureChannelFailure),
2906 WebExceptionStatus.SecureChannelFailure);
2909 else if (m_Error == WebExceptionStatus.Timeout)
2911 theException = new WebException(NetRes.GetWebStatusString("net_requestaborted", WebExceptionStatus.Timeout),
2912 WebExceptionStatus.Timeout);
2914 else if(m_Error == WebExceptionStatus.RequestCanceled)
2916 theException = new WebException(NetRes.GetWebStatusString("net_requestaborted", WebExceptionStatus.RequestCanceled),
2917 WebExceptionStatus.RequestCanceled,
2918 WebExceptionInternalStatus.RequestFatal,
2921 else if(m_Error == WebExceptionStatus.MessageLengthLimitExceeded ||
2922 m_Error == WebExceptionStatus.TrustFailure)
2924 theException = new WebException(NetRes.GetWebStatusString("net_connclosed", m_Error),
2926 WebExceptionInternalStatus.RequestFatal,
2931 if (m_Error == WebExceptionStatus.Success)
2933 throw new InternalException(); // TODO: replace it with a generic error for the product bits
2934 //m_Error = WebExceptionStatus.UnknownError;
2938 bool isolatedKeepAliveFailure = false;
2940 if (m_WriteList.Count != 1)
2942 // Real scenario: SSL against IIS-5 would fail if pipelinning.
2943 // retry = true will cover a general case when >>the server<< aborts a pipeline
2944 // Basically all pipelined requests are marked with recoverable error including the very active request.
2947 else if (m_Error == WebExceptionStatus.KeepAliveFailure)
2949 HttpWebRequest request = (HttpWebRequest) m_WriteList[0];
2950 // Check that the active request did not start the body yet
2951 if (!request.BodyStarted)
2952 isolatedKeepAliveFailure = true;
2955 retry = (!AtLeastOneResponseReceived && !((HttpWebRequest) m_WriteList[0]).BodyStarted);
2957 theException = new WebException(NetRes.GetWebStatusString("net_connclosed", m_Error),
2959 (isolatedKeepAliveFailure? WebExceptionInternalStatus.Isolated:
2960 retry? WebExceptionInternalStatus.Recoverable:
2961 WebExceptionInternalStatus.RequestFatal),
2966 WebException pipelineException = new WebException(NetRes.GetWebStatusString("net_connclosed", WebExceptionStatus.PipelineFailure),
2967 WebExceptionStatus.PipelineFailure,
2968 WebExceptionInternalStatus.Recoverable,
2971 requestArray = new HttpWebRequest[m_WriteList.Count];
2972 m_WriteList.CopyTo(requestArray, 0);
2973 ConnectionReturnResult.AddExceptionRange(ref returnResult, requestArray, pipelineException, theException);
2977 foreach (WaitListItem item in m_WaitList) {
2978 GlobalLog.Print("Request removed from WaitList#"+ValidationHelper.HashString(item.Request));
2981 foreach (HttpWebRequest request in m_WriteList) {
2982 GlobalLog.Print("Request removed from m_WriteList#"+ValidationHelper.HashString(request));
2986 m_WriteList.Clear();
2988 foreach (WaitListItem item in m_WaitList)
2990 NetworkingPerfCounters.Instance.IncrementAverage(NetworkingPerfCounterName.HttpWebRequestAvgQueueTime,
2991 item.QueueStartTime);
3000 GC.SuppressFinalize(this);
3002 if (!m_RemovedFromConnectionList && ConnectionGroup != null)
3004 m_RemovedFromConnectionList = true;
3005 ConnectionGroup.Disassociate(this);
3008 GlobalLog.Leave("Connection#" + ValidationHelper.HashString(this) + "::PrepareCloseConnectionSocket");
3014 HandleError - Handle a protocol error from the server.
3016 This method is called when we've detected some sort of fatal protocol
3017 violation while parsing a response, receiving data from the server,
3018 or failing to connect to the server. We'll fabricate
3019 a WebException and then call CloseConnection which closes the
3020 connection as well as informs the request through a callback.
3023 webExceptionStatus -
3030 internal void HandleConnectStreamException(bool writeDone, bool readDone, WebExceptionStatus webExceptionStatus, ref ConnectionReturnResult returnResult, Exception e)
3032 if (m_InnerException == null)
3034 m_InnerException = e;
3035 if (!(e is WebException) && NetworkStream is TlsStream)
3037 // Unless a WebException is passed the Connection knows better the error code if the transport is TlsStream
3038 webExceptionStatus = ((TlsStream) NetworkStream).ExceptionStatus;
3040 else if (e is ObjectDisposedException)
3042 webExceptionStatus = WebExceptionStatus.RequestCanceled;
3045 HandleError(writeDone, readDone, webExceptionStatus, ref returnResult);
3048 private void HandleErrorWithReadDone(WebExceptionStatus webExceptionStatus, ref ConnectionReturnResult returnResult)
3050 HandleError(false, true, webExceptionStatus, ref returnResult);
3053 private void HandleError(bool writeDone, bool readDone, WebExceptionStatus webExceptionStatus, ref ConnectionReturnResult returnResult)
3062 GlobalLog.Print("Connection#" + ValidationHelper.HashString(this) + "::HandleError() m_WriteList.Count:" + m_WriteList.Count +
3063 " m_WaitList.Count:" + m_WaitList.Count +
3064 " new WriteDone:" + m_WriteDone + " new ReadDone:" + m_ReadDone);
3065 GlobalLog.Print("Connection#" + ValidationHelper.HashString(this) + "::HandleError() current HttpWebRequest#" + ValidationHelper.HashString(m_CurrentRequest));
3067 if(webExceptionStatus == WebExceptionStatus.Success)
3068 throw new InternalException(); //consider making an assert later.
3070 m_Error = webExceptionStatus;
3072 PrepareCloseConnectionSocket(ref returnResult);
3073 // This will kill the socket
3074 // Must be done inside the lock. (Stream Close() isn't threadsafe.)
3076 FreeReadBuffer(); // Do it after close completes to insure buffer not in use
3080 private static void ReadCallbackWrapper(IAsyncResult asyncResult)
3082 if (asyncResult.CompletedSynchronously)
3087 ((Connection) asyncResult.AsyncState).ReadCallback(asyncResult);
3091 /// <para>Performs read callback processing on connection
3092 /// handles getting headers parsed and streams created</para>
3094 private void ReadCallback(IAsyncResult asyncResult)
3096 GlobalLog.Enter("Connection#" + ValidationHelper.HashString(this) + "::ReadCallback", ValidationHelper.HashString(asyncResult));
3099 WebExceptionStatus errorStatus = WebExceptionStatus.ReceiveFailure;
3102 // parameter validation
3104 GlobalLog.Assert(asyncResult != null, "Connection#{0}::ReadCallback()|asyncResult == null", ValidationHelper.HashString(this));
3105 GlobalLog.Assert((asyncResult is OverlappedAsyncResult || asyncResult is LazyAsyncResult), "Connection#{0}::ReadCallback()|asyncResult is not OverlappedAsyncResult.", ValidationHelper.HashString(this));
3108 bytesRead = EndRead(asyncResult);
3110 bytesRead = -1; // 0 is reserved for re-entry on already buffered data
3112 errorStatus = WebExceptionStatus.Success;
3114 catch (Exception exception) {
3115 // Notify request's SubmitWriteStream that a socket error happened. This will cause future writes to
3116 // throw an IOException.
3117 HttpWebRequest curRequest = m_CurrentRequest;
3118 if (curRequest != null)
3120 curRequest.ErrorStatusCodeNotify(this, false, true);
3124 if (m_InnerException == null)
3125 m_InnerException = exception;
3127 if (exception.GetType() == typeof(ObjectDisposedException))
3128 errorStatus = WebExceptionStatus.RequestCanceled;
3132 // Consider: In case of a async exception we should do minimal cleanup here trying the appDomain
3133 // to survive or to force unloading of the appDomain .
3134 // need to handle SSL errors too
3135 if (NetworkStream is TlsStream) {
3136 errorStatus = ((TlsStream) NetworkStream).ExceptionStatus;
3139 errorStatus = WebExceptionStatus.ReceiveFailure;
3142 errorStatus = WebExceptionStatus.ReceiveFailure;
3143 #endif // !FEATURE_PAL
3144 GlobalLog.Print("Connection#" + ValidationHelper.HashString(this) + "::ReadCallback() EndRead() errorStatus:" + errorStatus.ToString() + " caught exception:" + exception);
3147 ReadComplete(bytesRead, errorStatus);
3148 GlobalLog.Leave("Connection#" + ValidationHelper.HashString(this) + "::ReadCallback");
3153 /// <para>Attempts to poll the socket, to see if data is waiting to be read,
3154 /// if there is data there, then a read is started</para>
3156 internal void PollAndRead(HttpWebRequest request, bool userRetrievedStream) {
3157 GlobalLog.ThreadContract(ThreadKinds.Unknown, "Connection#" + ValidationHelper.HashString(this) + "::PollAndRead");
3159 // Ensure that we don't already have a response for this request, before we attempt to read the socket.
3160 request.NeedsToReadForResponse = true;
3161 GlobalLog.Print("Connection#" + ValidationHelper.HashString(this) + "::PollAndRead() InternalPeekCompleted:" + request.ConnectionReaderAsyncResult.InternalPeekCompleted.ToString() + " Result:" + ValidationHelper.ToString(request.ConnectionReaderAsyncResult.Result));
3162 if (request.ConnectionReaderAsyncResult.InternalPeekCompleted && request.ConnectionReaderAsyncResult.Result == null && CanBePooled)
3164 SyncRead(request, userRetrievedStream, true);
3168 // Peforms a [....] Read and calls the ReadComplete to process the result
3169 // The reads are done iteratively, until the Request has received enough
3170 // data to contruct a response, or a 100-Continue is read, allowing the HttpWebRequest
3171 // to return a write stream
3173 // probeRead = true only for POST request and when the caller needs to wait for 100-continue
3175 internal void SyncRead(HttpWebRequest request, bool userRetrievedStream, bool probeRead)
3177 GlobalLog.Enter("Connection#" + ValidationHelper.HashString(this) + "::SyncRead(byte[]) request#" + ValidationHelper.HashString(request) + (probeRead? ", Probe read = TRUE":string.Empty));
3178 GlobalLog.ThreadContract(ThreadKinds.Sync, "Connection#" + ValidationHelper.HashString(this) + "::SyncRead");
3180 // prevent recursive calls to this function
3181 if (t_SyncReadNesting > 0) {
3182 GlobalLog.Leave("Connection#" + ValidationHelper.HashString(this) + "::SyncRead() - nesting");
3186 bool pollSuccess = probeRead? false: true;
3189 t_SyncReadNesting++;
3191 // grab a counter to tell us whenever the SetRequestContinue is called
3192 int requestContinueCount = probeRead ? request.RequestContinueCount : 0;
3196 WebExceptionStatus errorStatus = WebExceptionStatus.ReceiveFailure;
3199 if (m_BytesScanned < m_BytesRead)
3201 // left over from previous read
3203 bytesRead = 0; //tell it we want to use buffered data on the first iteration
3204 errorStatus = WebExceptionStatus.Success;
3213 errorStatus = WebExceptionStatus.ReceiveFailure;
3217 pollSuccess = Poll(request.ContinueTimeout * 1000, SelectMode.SelectRead); // Timeout is in microseconds
3218 GlobalLog.Print("Connection#" + ValidationHelper.HashString(this) + "::SyncRead() PollSuccess : " + pollSuccess);
3223 //Ensures that we'll timeout eventually on an appdomain unload.
3224 //Will be a no-op if the timeout doesn't change from request to request.
3225 ReadTimeout = request.Timeout;
3227 bytesRead = Read(m_ReadBuffer, m_BytesRead, m_ReadBuffer.Length - m_BytesRead);
3228 errorStatus = WebExceptionStatus.Success;
3230 bytesRead = -1; // 0 is reserved for re-entry on already buffered data
3234 catch (Exception exception)
3236 if (NclUtilities.IsFatal(exception)) throw;
3238 if (m_InnerException == null)
3239 m_InnerException = exception;
3241 if (exception.GetType() == typeof(ObjectDisposedException))
3242 errorStatus = WebExceptionStatus.RequestCanceled;
3244 // need to handle SSL errors too
3246 else if (NetworkStream is TlsStream) {
3247 errorStatus = ((TlsStream)NetworkStream).ExceptionStatus;
3249 #endif // !FEATURE_PAL
3252 SocketException socketException = exception.InnerException as SocketException;
3253 if (socketException != null)
3255 if (socketException.ErrorCode == (int) SocketError.TimedOut)
3256 errorStatus = WebExceptionStatus.Timeout;
3258 errorStatus = WebExceptionStatus.ReceiveFailure;
3262 GlobalLog.Print("Connection#" + ValidationHelper.HashString(this) + "::SyncRead() Read() threw errorStatus:" + errorStatus.ToString() + " bytesRead:" + bytesRead.ToString());
3266 requestDone = ReadComplete(bytesRead, errorStatus);
3269 } while (!requestDone && (userRetrievedStream || requestContinueCount == request.RequestContinueCount));
3272 t_SyncReadNesting--;
3277 // [....] 100-Continue wait only
3278 request.FinishContinueWait();
3281 if (!request.Saw100Continue && !userRetrievedStream)
3283 //During polling, we got a response that wasn't a 100 continue.
3284 request.NeedsToReadForResponse = false;
3289 GlobalLog.Print("Connection#" + ValidationHelper.HashString(this) + "::SyncRead() Poll has timed out, calling SetRequestContinue().");
3290 request.SetRequestContinue();
3293 GlobalLog.Leave("Connection#" + ValidationHelper.HashString(this) + "::SyncRead()");
3298 // Performs read callback processing on connection
3299 // handles getting headers parsed and streams created
3301 // bytesRead == 0 when we re-enter on buffered data without doing actual read
3302 // bytesRead == -1 when we got a connection close plus when errorStatus == sucess we got a g----ful close.
3303 // Otheriwse bytesRead is read byted to add to m_BytesRead i.e. to previously buffered data
3305 private bool ReadComplete(int bytesRead, WebExceptionStatus errorStatus)
3307 bool requestDone = true;
3308 CoreResponseData continueResponseData = null;
3309 ConnectionReturnResult returnResult = null;
3310 HttpWebRequest currentRequest = null;
3314 GlobalLog.Print("Connection#" + ValidationHelper.HashString(this) + "::ReadComplete() m_BytesRead:" + m_BytesRead.ToString() + " m_BytesScanned:" + m_BytesScanned + " (+= new bytesRead:" + bytesRead.ToString() + ")");
3318 // Means we might have gotten g----full or hard connection close.
3320 // If this is the first thing we read for a request then it
3321 // could be an idle connection closed by the server (isolated error)
3322 if (m_ReadState == ReadState.Start && m_AtLeastOneResponseReceived)
3324 // Note that KeepAliveFailure will be checked against POST-type requests
3325 // and it's fatal if the body was already started.
3326 if (errorStatus == WebExceptionStatus.Success || errorStatus == WebExceptionStatus.ReceiveFailure)
3327 errorStatus = WebExceptionStatus.KeepAliveFailure;
3329 else if (errorStatus == WebExceptionStatus.Success)
3331 // we got unexpected FIN in the middle of the response, or on a fresh connection, that's fatal
3332 errorStatus = WebExceptionStatus.ConnectionClosed;
3335 // Notify request's SubmitWriteStream that a socket error happened. This will cause future writes to
3336 // throw an IOException.
3337 HttpWebRequest curRequest = m_CurrentRequest;
3338 if (curRequest != null)
3340 curRequest.ErrorStatusCodeNotify(this, false, true);
3343 HandleErrorWithReadDone(errorStatus, ref returnResult);
3347 // Otherwise, we've got data.
3348 GlobalLog.Dump(m_ReadBuffer, m_BytesScanned, m_BytesRead - m_BytesScanned);
3349 GlobalLog.Dump(m_ReadBuffer, m_BytesRead, bytesRead);
3352 bytesRead += m_BytesRead;
3353 if (bytesRead > m_ReadBuffer.Length)
3354 throw new InternalException(); //in case we posted two receives at once
3355 m_BytesRead = bytesRead;
3357 // We have the parsing code seperated out in ParseResponseData
3359 // If we don't have all the headers yet. Resubmit the receive,
3360 // passing in the bytes read total as our index. When we get
3361 // back here we'll end up reparsing from the beginning, which is
3362 // OK. because this shouldn't be a performance case.
3364 //if we're back here, we need to reset the scanned bytes to 0.
3366 DataParseStatus parseStatus = ParseResponseData(ref returnResult, out requestDone, out continueResponseData);
3368 // copy off m_CurrentRequest as we might start processing a next request before exiting this method
3369 currentRequest = m_CurrentRequest;
3371 if (parseStatus != DataParseStatus.NeedMoreData)
3372 m_CurrentRequest = null;
3374 if (parseStatus == DataParseStatus.Invalid || parseStatus == DataParseStatus.DataTooBig)
3376 // Tell the request's SubmitWriteStream that the connection will be closed. It should ---- any
3377 // future writes so that the appropriate exception will be received in GetResponse().
3378 if (currentRequest != null)
3380 currentRequest.ErrorStatusCodeNotify(this, false, false);
3386 if (parseStatus == DataParseStatus.Invalid) {
3387 HandleErrorWithReadDone(WebExceptionStatus.ServerProtocolViolation, ref returnResult);
3390 HandleErrorWithReadDone(WebExceptionStatus.MessageLengthLimitExceeded, ref returnResult);
3392 GlobalLog.Print("Connection#" + ValidationHelper.HashString(this) + "::ReadComplete() parseStatus:" + parseStatus + " returnResult:" + returnResult);
3396 //Done means the ConnectStream take care of this connection until ConnectStream.CallDone()
3397 else if (parseStatus == DataParseStatus.Done)
3399 GlobalLog.Print("Connection#" + ValidationHelper.HashString(this) + "::ReadComplete() [The response stream is ready] parseStatus = DataParseStatus.Done");
3404 // we may reach the end of our buffer only when parsing headers.
3405 // this can happen when the header section is bigger than our initial 4k guess
3406 // which should be a good assumption in 99.9% of the cases. what we do here is:
3407 // 1) if there's a single BIG header (bigger than the current size) we will need to
3408 // grow the buffer before we move data over and read more data.
3409 // 2) move unparsed data to the beginning of the buffer and read more data in the
3410 // remaining part of the data.
3412 if (parseStatus == DataParseStatus.NeedMoreData)
3414 GlobalLog.Print("Connection#" + ValidationHelper.HashString(this) + "::ReadComplete() OLD buffer. m_ReadBuffer.Length:" + m_ReadBuffer.Length.ToString() + " m_BytesRead:" + m_BytesRead.ToString() + " m_BytesScanned:" + m_BytesScanned.ToString());
3415 int unparsedDataSize = m_BytesRead - m_BytesScanned;
3416 if (unparsedDataSize != 0)
3418 if (m_BytesScanned == 0 && m_BytesRead == m_ReadBuffer.Length)
3421 // 1) we need to grow the buffer, move the unparsed data to the beginning of the buffer before reading more data.
3422 // since the buffer size is 4k, should we just double
3424 byte[] newReadBuffer = new byte[m_ReadBuffer.Length * 2 /*+ ReadBufferSize*/];
3425 Buffer.BlockCopy(m_ReadBuffer, 0, newReadBuffer, 0, m_BytesRead);
3427 // if m_ReadBuffer is from the pinnable cache, give it back
3429 m_ReadBuffer = newReadBuffer;
3434 // just move data around in the same buffer.
3436 Buffer.BlockCopy(m_ReadBuffer, m_BytesScanned, m_ReadBuffer, 0, unparsedDataSize);
3440 // update indexes and offsets in the new buffer
3442 m_BytesRead = unparsedDataSize;
3444 GlobalLog.Print("Connection#" + ValidationHelper.HashString(this) + "::ReadComplete() NEW or shifted buffer. m_ReadBuffer.Length:" + m_ReadBuffer.Length.ToString() + " m_BytesRead:" + m_BytesRead.ToString() + " m_BytesScanned:" + m_BytesScanned.ToString());
3446 if (currentRequest != null)
3449 // This case means that we still parsing the headers, so need to post another read in the async case
3451 if (currentRequest.Async)
3453 GlobalLog.Print("Connection#" + ValidationHelper.HashString(this) + "::ReadComplete() Reposting Async Read. Buffer:" + ValidationHelper.HashString(m_ReadBuffer) + " BytesScanned:" + m_BytesScanned.ToString());
3455 if (Thread.CurrentThread.IsThreadPoolThread)
3457 GlobalLog.Print("Connection#" + ValidationHelper.HashString(this) + "::ReadComplete() Calling PostReceive().");
3462 // Offload to the threadpool to protect against the case where one request's thread posts IO that another request
3463 // depends on, but the first thread dies in the mean time.
3464 GlobalLog.Print("Connection#" + ValidationHelper.HashString(this) + "::ReadComplete() ThreadPool.UnsafeQueueUserWorkItem(m_PostReceiveDelegate, this)");
3465 ThreadPool.UnsafeQueueUserWorkItem(m_PostReceiveDelegate, this);
3472 // Any exception is processed by HandleError() and ----ed to avoid throwing on a thread pool
3473 // In the [....] case the HandleError() will abort the request so the caller will pick up the result.
3475 catch (Exception exception) {
3476 if (NclUtilities.IsFatal(exception)) throw;
3480 if (m_InnerException == null)
3481 m_InnerException = exception;
3483 // Notify request's SubmitWriteStream that a socket error happened. This will cause future writes to
3484 // throw an IOException.
3485 HttpWebRequest curRequest = m_CurrentRequest;
3486 if (curRequest != null)
3488 curRequest.ErrorStatusCodeNotify(this, false, true);
3491 HandleErrorWithReadDone(WebExceptionStatus.ReceiveFailure, ref returnResult);
3496 // It is only safe to continue if there was a 100 continue OR buffering is supported.
3497 if (currentRequest != null && currentRequest.HttpWriteMode != HttpWriteMode.None &&
3498 (continueResponseData != null
3499 // not a 100 continue, but we have buffering so we don't care what it was.
3500 || (returnResult != null && returnResult.IsNotEmpty && currentRequest.AllowWriteStreamBuffering)
3504 // if returnResult is not empty it must also contain some result for the currently active request
3505 // Since it could be a POST request waiting on the body submit, signal the body here
3506 if (currentRequest.FinishContinueWait())
3508 currentRequest.SetRequestContinue(continueResponseData);
3513 ConnectionReturnResult.SetResponses(returnResult);
3520 // This method is called by ConnectStream, only when resubmitting
3521 // We have sent the headers already in HttpWebRequest.EndSubmitRequest()
3522 // which calls ConnectStream.WriteHeaders() which calls to HttpWebRequest.EndWriteHeaders()
3523 // which calls ConnectStream.ResubmitWrite() which calls in here
3524 internal void Write(ScatterGatherBuffers writeBuffer) {
3525 GlobalLog.Enter("Connection#" + ValidationHelper.HashString(this) + "::Write(ScatterGatherBuffers) networkstream#" + ValidationHelper.HashString(NetworkStream));
3527 // parameter validation
3529 GlobalLog.Assert(writeBuffer != null, "Connection#{0}::Write(ScatterGatherBuffers)|writeBuffer == null", ValidationHelper.HashString(this));
3531 // set up array for MultipleWrite call
3532 // note that GetBuffers returns null if we never wrote to it.
3534 BufferOffsetSize[] buffers = writeBuffer.GetBuffers();
3535 if (buffers!=null) {
3537 // This will block writing the buffers out.
3539 MultipleWrite(buffers);
3541 GlobalLog.Leave("Connection#" + ValidationHelper.HashString(this) + "::Write(ScatterGatherBuffers) this:" + ValidationHelper.HashString(this) + " writeBuffer.Size:" + writeBuffer.Length.ToString());
3547 PostReceiveWrapper - Post a receive from a worker thread.
3549 This is our delegate, used for posting receives from a worker thread.
3550 We do this when we can't be sure that we're already on a worker thread,
3551 and we don't want to post from a client thread because if it goes away
3556 state - a null object
3561 private static void PostReceiveWrapper(object state) {
3562 Connection thisConnection = state as Connection;
3563 GlobalLog.Enter("Connection#" + ValidationHelper.HashString(thisConnection) + "::PostReceiveWrapper", "Cnt#" + ValidationHelper.HashString(thisConnection));
3564 GlobalLog.Assert(thisConnection != null, "Connection#{0}::PostReceiveWrapper()|thisConnection == null", ValidationHelper.HashString(thisConnection));
3566 thisConnection.PostReceive();
3568 GlobalLog.Leave("Connection#" + ValidationHelper.HashString(thisConnection) + "::PostReceiveWrapper");
3571 private void PostReceive()
3573 GlobalLog.Enter("Connection#" + ValidationHelper.HashString(this) + "::PostReceive", "");
3575 GlobalLog.Print("Connection#" + ValidationHelper.HashString(this) + "::PostReceive() m_ReadBuffer:" + ValidationHelper.HashString(m_ReadBuffer) + " Length:" + m_ReadBuffer.Length.ToString());
3579 GlobalLog.Assert(m_BytesScanned == 0, "PostReceive()|A receive should not be posted when m_BytesScanned != 0 (the data should be moved to offset 0).");
3581 if (m_LastAsyncResult != null && !m_LastAsyncResult.IsCompleted)
3582 throw new InternalException(); //This may cause duplicate requests if we let it through in retail
3585 m_LastAsyncResult = UnsafeBeginRead(m_ReadBuffer, m_BytesRead, m_ReadBuffer.Length - m_BytesRead, m_ReadCallback, this);
3586 if (m_LastAsyncResult.CompletedSynchronously)
3589 ReadCallback(m_LastAsyncResult);
3592 catch (Exception exception) {
3593 // Notify request's SubmitWriteStream that a socket error happened. This will cause future writes to
3594 // throw an IOException.
3595 HttpWebRequest curRequest = m_CurrentRequest;
3596 if (curRequest != null)
3598 curRequest.ErrorStatusCodeNotify(this, false, true);
3602 ConnectionReturnResult returnResult = null;
3603 HandleErrorWithReadDone(WebExceptionStatus.ReceiveFailure, ref returnResult);
3604 ConnectionReturnResult.SetResponses(returnResult);
3605 GlobalLog.LeaveException("Connection#" + ValidationHelper.HashString(this) + "::PostReceive", exception);
3608 GlobalLog.Leave("Connection#" + ValidationHelper.HashString(this) + "::PostReceive");
3613 private static void TunnelThroughProxyWrapper(IAsyncResult result){
3614 if(result.CompletedSynchronously){
3618 bool success = false;
3619 WebExceptionStatus ws = WebExceptionStatus.ConnectFailure;
3620 HttpWebRequest req = (HttpWebRequest)((LazyAsyncResult)result).AsyncObject;
3621 Connection conn = (Connection)((TunnelStateObject)result.AsyncState).Connection;
3622 HttpWebRequest originalReq = (HttpWebRequest)((TunnelStateObject)result.AsyncState).OriginalRequest;
3624 GlobalLog.Enter("Connection#" + ValidationHelper.HashString(conn) + "::TunnelThroughProxyCallback");
3627 req.EndGetResponse(result);
3628 HttpWebResponse connectResponse = (HttpWebResponse)req.GetResponse();
3629 ConnectStream connectStream = (ConnectStream)connectResponse.GetResponseStream();
3631 // this stream will be used as the real stream for TlsStream
3632 conn.NetworkStream = new NetworkStream(connectStream.Connection.NetworkStream, true);
3633 // This will orphan the original connect stream now owned by tunnelStream
3634 connectStream.Connection.NetworkStream.ConvertToNotSocketOwner();
3638 catch (Exception exception) {
3639 if (conn.m_InnerException == null)
3640 conn.m_InnerException = exception;
3642 if (exception is WebException) {
3643 ws = ((WebException)exception).Status;
3646 GlobalLog.Print("Connection#" + ValidationHelper.HashString(conn) + "::TunnelThroughProxyCallback() exception occurred: " + exception);
3650 ConnectionReturnResult returnResult = null;
3651 conn.HandleError(false, false, ws, ref returnResult);
3652 ConnectionReturnResult.SetResponses(returnResult);
3656 conn.CompleteConnection(true, originalReq);
3657 GlobalLog.Leave("Connection#" + ValidationHelper.HashString(conn) + "::TunnelThroughProxyCallback");
3664 private bool TunnelThroughProxy(Uri proxy, HttpWebRequest originalRequest, bool async) {
3665 GlobalLog.Enter("Connection#" + ValidationHelper.HashString(this) + "::TunnelThroughProxy", "proxy="+proxy+", async="+async+", originalRequest #"+ValidationHelper.HashString(originalRequest));
3667 bool result = false;
3668 HttpWebRequest connectRequest = null;
3669 HttpWebResponse connectResponse = null;
3672 (new WebPermission(NetworkAccess.Connect, proxy)).Assert();
3674 connectRequest = new HttpWebRequest(
3676 originalRequest.Address,
3677 // new Uri("https://" + originalRequest.Address.GetParts(UriComponents.HostAndPort, UriFormat.UriEscaped)),
3682 WebPermission.RevertAssert();
3685 connectRequest.Credentials = originalRequest.InternalProxy == null ? null : originalRequest.InternalProxy.Credentials;
3686 connectRequest.InternalProxy = null;
3687 connectRequest.PreAuthenticate = true;
3690 TunnelStateObject o = new TunnelStateObject(originalRequest, this);
3691 IAsyncResult asyncResult = connectRequest.BeginGetResponse(m_TunnelCallback, o);
3692 if(!asyncResult.CompletedSynchronously){
3693 GlobalLog.Leave("Connection#" + ValidationHelper.HashString(this) + "::TunnelThroughProxy completed asynchronously", true);
3696 connectResponse = (HttpWebResponse)connectRequest.EndGetResponse(asyncResult);
3699 connectResponse = (HttpWebResponse)connectRequest.GetResponse();
3702 ConnectStream connectStream = (ConnectStream)connectResponse.GetResponseStream();
3704 // this stream will be used as the real stream for TlsStream
3705 NetworkStream = new NetworkStream(connectStream.Connection.NetworkStream, true);
3706 // This will orphan the original connect stream now owned by tunnelStream
3707 connectStream.Connection.NetworkStream.ConvertToNotSocketOwner();
3710 catch (Exception exception) {
3711 if (m_InnerException == null)
3712 m_InnerException = exception;
3713 GlobalLog.Print("Connection#" + ValidationHelper.HashString(this) + "::TunnelThroughProxy() exception occurred: " + exception);
3716 GlobalLog.Leave("Connection#" + ValidationHelper.HashString(this) + "::TunnelThroughProxy", result);
3722 // CheckNonIdle - called after update of the WriteList/WaitList,
3723 // upon the departure of our Idle state our, BusyCount will
3724 // go to non-0, then we need to mark this transition
3727 private void CheckNonIdle() {
3728 GlobalLog.Print("Connection#" + ValidationHelper.HashString(this) + "::CheckNonIdle()");
3729 if (m_Idle && this.BusyCount != 0) {
3731 ServicePoint.IncrementConnection();
3732 ConnectionGroup.IncrementConnection();
3737 // CheckIdle - called after update of the WriteList/WaitList,
3738 // specifically called after we remove entries
3741 private void CheckIdle() {
3742 // The timer thread is allowed to call this. (It doesn't call user code and doesn't block.)
3743 GlobalLog.ThreadContract(ThreadKinds.Unknown, ThreadKinds.SafeSources | ThreadKinds.Finalization | ThreadKinds.Timer, "Connection#" + ValidationHelper.HashString(this) + "::CheckIdle");
3745 GlobalLog.Print("Connection#" + ValidationHelper.HashString(this) + "::CheckIdle() m_Idle = " + m_Idle + ", BusyCount = " + BusyCount);
3746 if (!m_Idle && this.BusyCount == 0) {
3748 ServicePoint.DecrementConnection();
3749 if (ConnectionGroup != null) {
3750 GlobalLog.Print("Connection#" + ValidationHelper.HashString(this) + "::CheckIdle() - calling ConnectionGoneIdle()");
3751 ConnectionGroup.DecrementConnection();
3752 ConnectionGroup.ConnectionGoneIdle();
3754 // Remember the moment when this connection went idle.
3755 m_IdleSinceUtc = DateTime.UtcNow;
3760 // DebugDumpArrayListEntries - debug goop
3762 [Conditional("TRAVE")]
3763 private void DebugDumpWriteListEntries() {
3764 for (int i = 0; i < m_WriteList.Count; i++)
3766 DebugDumpListEntry(i, m_WriteList[i] as HttpWebRequest, "WriteList");
3770 [Conditional("TRAVE")]
3771 private void DebugDumpWaitListEntries() {
3772 for (int i = 0; i < m_WaitList.Count; i++)
3774 DebugDumpListEntry(i, m_WaitList[i].Request, "WaitList");
3778 [Conditional("TRAVE")]
3779 private void DebugDumpListEntry(int currentPos, HttpWebRequest req, string listType) {
3780 GlobalLog.Print("WaitList[" + currentPos.ToString() + "] Req: #" +
3781 ValidationHelper.HashString(req));
3785 // Validation & debugging
3787 [System.Diagnostics.Conditional("DEBUG")]
3788 internal void DebugMembers(int requestHash) {
3790 bool dump = requestHash==0;
3791 GlobalLog.Print("Cnt#" + this.GetHashCode());
3793 foreach(HttpWebRequest request in m_WriteList) {
3794 if (request.GetHashCode() == requestHash) {
3795 GlobalLog.Print("Found-WriteList");
3800 foreach(WaitListItem item in m_WaitList) {
3801 if (item.Request.GetHashCode() == requestHash) {
3802 GlobalLog.Print("Found-WaitList");
3810 DebugDumpWriteListEntries();
3811 DebugDumpWaitListEntries();
3817 [System.Diagnostics.Conditional("TRAVE")]
3818 internal void Dump() {
3819 GlobalLog.Print("CanPipeline:" + m_CanPipeline);
3820 GlobalLog.Print("Pipelining:" + m_Pipelining);
3821 GlobalLog.Print("KeepAlive:" + m_KeepAlive);
3822 GlobalLog.Print("m_Error:" + m_Error);
3823 GlobalLog.Print("m_ReadBuffer:" + m_ReadBuffer);
3824 GlobalLog.Print("m_BytesRead:" + m_BytesRead);
3825 GlobalLog.Print("m_BytesScanned:" + m_BytesScanned);
3826 GlobalLog.Print("m_ResponseData:" + m_ResponseData);
3827 GlobalLog.Print("m_ReadState:" + m_ReadState);
3828 GlobalLog.Print("m_StatusState:" + m_StatusState);
3829 GlobalLog.Print("ConnectionGroup:" + ConnectionGroup);
3830 GlobalLog.Print("Idle:" + m_Idle);
3831 GlobalLog.Print("ServicePoint:" + ServicePoint);
3832 GlobalLog.Print("m_Version:" + ServicePoint.ProtocolVersion);
3833 GlobalLog.Print("NetworkStream:" + NetworkStream);
3835 if ( NetworkStream is TlsStream) {
3836 TlsStream tlsStream = NetworkStream as TlsStream;
3837 tlsStream.DebugMembers();
3840 #endif // !FEATURE_PAL
3841 if (NetworkStream != null) {
3842 NetworkStream.DebugMembers();
3844 GlobalLog.Print("m_AbortDelegate:" + m_AbortDelegate);
3845 GlobalLog.Print("ReadDone:" + m_ReadDone);
3846 GlobalLog.Print("WriteDone:" + m_WriteDone);
3847 GlobalLog.Print("Free:" + m_Free);