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;
20 internal enum ReadState {
22 StatusLine, // about to parse status line
23 Headers, // reading headers
27 internal enum DataParseStatus {
28 NeedMoreData = 0, // need more data
29 ContinueParsing, // continue parsing
31 Invalid, // bad data format
32 DataTooBig, // data exceeds the allowed size
35 internal enum WriteBufferState {
42 // The enum lietrals will be displayed to the user in the exception message
43 internal enum WebParseErrorSection {
50 // The enum literal will be used to look up an error string in the resource file
51 internal enum WebParseErrorCode {
58 UnexpectedServerResponse
61 // Only defined for DataParseStatus.Invalid
62 struct WebParseError {
63 public WebParseErrorSection Section;
64 public WebParseErrorCode Code;
68 struct TunnelStateObject {
69 internal TunnelStateObject(HttpWebRequest r, Connection c){
74 internal Connection Connection;
75 internal HttpWebRequest OriginalRequest;
79 // ConnectionReturnResult - used to spool requests that have been completed,
80 // and need to be notified.
83 internal class ConnectionReturnResult {
85 private static readonly WaitCallback s_InvokeConnectionCallback = new WaitCallback(InvokeConnectionCallback);
87 private struct RequestContext {
88 internal HttpWebRequest Request;
89 internal object CoreResponse;
91 internal RequestContext(HttpWebRequest request, object coreResponse)
94 CoreResponse = coreResponse;
98 private List<RequestContext> m_Context;
100 internal ConnectionReturnResult()
102 m_Context = new List<RequestContext>(5);
105 internal ConnectionReturnResult(int capacity)
107 m_Context = new List<RequestContext>(capacity);
110 internal bool IsNotEmpty {
112 return m_Context.Count != 0;
116 internal static void Add(ref ConnectionReturnResult returnResult, HttpWebRequest request, CoreResponseData coreResponseData)
118 if (coreResponseData == null)
119 throw new InternalException(); //This may cause duplicate requests if we let it through in retail
121 if (returnResult == null) {
122 returnResult = new ConnectionReturnResult();
126 //This may cause duplicate requests if we let it through in retail but it's may be expensive to catch here
127 for (int j = 0; j < returnResult.m_Context.Count; ++j)
128 if ((object)returnResult.m_Context[j].Request == (object) request)
129 throw new InternalException();
132 returnResult.m_Context.Add(new RequestContext(request, coreResponseData));
135 internal static void AddExceptionRange(ref ConnectionReturnResult returnResult, HttpWebRequest [] requests, Exception exception)
137 AddExceptionRange(ref returnResult, requests, exception, exception);
139 internal static void AddExceptionRange(ref ConnectionReturnResult returnResult, HttpWebRequest [] requests, Exception exception, Exception firstRequestException)
142 //This may cause duplicate requests if we let it through in retail
143 if (exception == null)
144 throw new InternalException();
146 if (returnResult == null) {
147 returnResult = new ConnectionReturnResult(requests.Length);
149 // "abortedRequestExeption" is assigned to the "abortedRequest" or to the very first request if the latest is null
150 // Everyone else will get "exception"
151 for (int i = 0; i < requests.Length; ++i)
154 //This may cause duplicate requests if we let it through in retail but it's may be expensive to catch here
155 for (int j = 0; j < returnResult.m_Context.Count; ++j)
156 if ((object)returnResult.m_Context[j].Request == (object) requests[i])
157 throw new InternalException();
161 returnResult.m_Context.Add(new RequestContext(requests[i], firstRequestException));
163 returnResult.m_Context.Add(new RequestContext(requests[i], exception));
167 internal static void SetResponses(ConnectionReturnResult returnResult) {
168 if (returnResult==null){
172 GlobalLog.Print("ConnectionReturnResult#" + ValidationHelper.HashString(returnResult) + "::SetResponses() count=" + returnResult.m_Context.Count.ToString());
173 for (int i = 0; i < returnResult.m_Context.Count; i++)
176 HttpWebRequest request = returnResult.m_Context[i].Request;
178 CoreResponseData coreResponseData = returnResult.m_Context[i].CoreResponse as CoreResponseData;
179 if (coreResponseData == null)
180 GlobalLog.DebugRemoveRequest(request);
182 request.SetAndOrProcessResponse(returnResult.m_Context[i].CoreResponse);
186 // on error, with more than one callback need to queue others off to another thread
188 GlobalLog.Print("ConnectionReturnResult#" + ValidationHelper.HashString(returnResult) + "::Exception"+e);
189 returnResult.m_Context.RemoveRange(0,(i+1));
190 if (returnResult.m_Context.Count > 0)
192 ThreadPool.UnsafeQueueUserWorkItem(s_InvokeConnectionCallback, returnResult);
198 returnResult.m_Context.Clear();
201 private static void InvokeConnectionCallback(object objectReturnResult)
203 ConnectionReturnResult returnResult = (ConnectionReturnResult)objectReturnResult;
204 SetResponses(returnResult);
209 // Connection - this is the Connection used to parse
210 // server responses, queue requests, and pipeline requests
212 internal class Connection : PooledStream {
215 // thread statics - these values must be per thread, because
216 // other requests and operations can take place concurrently on this Connection.
217 // Our concern is to make sure that a nested call does not get confused with an
218 // operation on another thread. Parameter passing cannot be used, because
219 // the call stack may exit and then reenter the same Connection object.
222 private static int t_SyncReadNesting;
224 private const int CRLFSize = 2;
225 private const long c_InvalidContentLength = -2L;
228 // Buffer manager that allocates and reuses 4k buffers.
230 private const int CachedBufferSize = 4096;
231 private static PinnableBufferCache s_PinnableBufferCache = new PinnableBufferCache("System.Net.Connection", CachedBufferSize);
234 // Little status line holder.
236 private class StatusLineValues
238 internal int MajorVersion;
239 internal int MinorVersion;
240 internal int StatusCode;
241 internal string StatusDescription;
244 private class WaitListItem
246 private HttpWebRequest request;
247 private long queueStartTime;
249 public HttpWebRequest Request
251 get { return request; }
254 public long QueueStartTime
256 get { return queueStartTime; }
259 public WaitListItem(HttpWebRequest request, long queueStartTime)
261 this.request = request;
262 this.queueStartTime = queueStartTime;
269 private WebExceptionStatus m_Error;
270 internal Exception m_InnerException;
273 internal int m_IISVersion = -1; //-1 means unread
274 private byte[] m_ReadBuffer;
275 private bool m_ReadBufferFromPinnableCache; // If we get our m_readBuffer from the Pinnable cache we have to explicitly free it
276 private int m_BytesRead;
277 private int m_BytesScanned;
278 private int m_TotalResponseHeadersLength;
279 private int m_MaximumResponseHeadersLength;
280 private long m_MaximumUnauthorizedUploadLength;
281 private CoreResponseData m_ResponseData;
282 private ReadState m_ReadState;
283 private StatusLineValues m_StatusLineValues;
284 private int m_StatusState;
285 private List<WaitListItem> m_WaitList;
286 private ArrayList m_WriteList;
287 private IAsyncResult m_LastAsyncResult;
288 private TimerThread.Timer m_RecycleTimer;
289 private WebParseError m_ParseError;
290 private bool m_AtLeastOneResponseReceived;
292 private static readonly WaitCallback m_PostReceiveDelegate = new WaitCallback(PostReceiveWrapper);
293 private static readonly AsyncCallback m_ReadCallback = new AsyncCallback(ReadCallbackWrapper);
294 private static readonly AsyncCallback m_TunnelCallback = new AsyncCallback(TunnelThroughProxyWrapper);
295 private static byte[] s_NullBuffer = new byte[0];
298 // Abort handling variables. When trying to abort the
299 // connection, we set Aborted = true, and close m_AbortSocket
300 // if its non-null. m_AbortDelegate, is returned to every
301 // request from our SubmitRequest method. Calling m_AbortDelegate
302 // drives us into Abort mode.
304 private HttpAbortDelegate m_AbortDelegate;
305 private ConnectionGroup m_ConnectionGroup;
307 private UnlockConnectionDelegate m_ConnectionUnlock;
310 // ReadDone and m_Write - no two vars are so complicated,
311 // as these two. Used for m_WriteList managment, most be under crit
312 // section when accessing.
314 // ReadDone tracks the item at the end or
315 // just recenlty removed from the m_WriteList. While a
316 // pending BeginRead is in place, we need this to be false, in
317 // order to indicate to tell the WriteDone callback, that we can
318 // handle errors/resets. The only exception is when the m_WriteList
319 // is empty, and there are no outstanding requests, then all it can
322 // WriteDone tracks the item just added at the begining of the m_WriteList.
323 // this needs to be false while we about to write something, but have not
324 // yet begin or finished the write. Upon completion, its set to true,
325 // so that DoneReading/ReadStartNextRequest can close the socket, without fear
326 // of a errand writer still banging away on another thread.
329 private DateTime m_IdleSinceUtc;
330 private HttpWebRequest m_LockedRequest;
331 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.
332 private bool m_CanPipeline;
333 private bool m_Free = true;
334 private bool m_Idle = true;
335 private bool m_KeepAlive = true;
336 private bool m_Pipelining;
337 private int m_ReservedCount;
338 private bool m_ReadDone;
339 private bool m_WriteDone;
340 private bool m_RemovedFromConnectionList;
341 private bool m_NonKeepAliveRequestPipelined;
343 // Pipeline Throttling: m_IsPipelinePaused==true when we stopped and false when it's ok to add to the pipeline.
344 private bool m_IsPipelinePaused;
345 private static int s_MaxPipelinedCount = 10;
346 private static int s_MinPipelinedCount = 5;
349 private bool q_Tunnelling;
352 internal override ServicePoint ServicePoint {
354 return ConnectionGroup.ServicePoint;
358 private ConnectionGroup ConnectionGroup {
360 return m_ConnectionGroup;
365 // LockedRequest is the request that needs exclusive access to this connection
366 // the ConnectionGroup should proctect the Connection object from any new
367 // Requests being queued, until this m_LockedRequest is finished.
369 internal HttpWebRequest LockedRequest {
371 return m_LockedRequest;
374 HttpWebRequest myLock = m_LockedRequest;
376 GlobalLog.Print("Connection#" + ValidationHelper.HashString(this) + "::LockedRequest_set() old#"+ ((myLock!=null)?myLock.GetHashCode().ToString():"null") + " new#" + ((value!=null)?value.GetHashCode().ToString():"null"));
378 if ((object)value == (object)myLock)
380 if (value != null && (object)value.UnlockConnectionDelegate != (object) m_ConnectionUnlock)
382 throw new InternalException();
387 object myDelegate = myLock == null? null: myLock.UnlockConnectionDelegate;
388 if (myDelegate != null && (value != null || (object)m_ConnectionUnlock != (object)myDelegate))
389 throw new InternalException();
393 m_LockedRequest = null;
394 myLock.UnlockConnectionDelegate = null;
398 UnlockConnectionDelegate chkDelegate = value.UnlockConnectionDelegate;
400 // If "value" request was already locking a connection that is not "this", unlock that other connection
402 if ((object)chkDelegate != null)
404 if ((object)chkDelegate == (object)m_ConnectionUnlock)
405 throw new InternalException();
407 GlobalLog.Print("Connection#" + ValidationHelper.HashString(this) + "::LockedRequest_set() Unlocking old request Connection");
411 value.UnlockConnectionDelegate = m_ConnectionUnlock;
412 m_LockedRequest = value;
419 /// Delegate called when the request is finished using this Connection
420 /// exclusively. Called in Abort cases and after NTLM authenticaiton completes.
423 private void UnlockRequest() {
424 GlobalLog.Print("Connection#" + ValidationHelper.HashString(this) + "::UnlockRequest() LockedRequest#" + ValidationHelper.HashString(LockedRequest));
426 LockedRequest = null;
428 if (ConnectionGroup != null) {
429 GlobalLog.Print("Connection#" + ValidationHelper.HashString(this) + "::UnlockRequest() - forcing call to ConnectionGoneIdle()");
430 ConnectionGroup.ConnectionGoneIdle();
437 private string MyLocalEndPoint {
440 return NetworkStream.InternalSocket.LocalEndPoint.ToString();
443 return "no connection";
449 private string MyLocalPort {
452 if (NetworkStream == null || !NetworkStream.Connected) {
453 return "no connection";
455 return ((IPEndPoint)NetworkStream.InternalSocket.LocalEndPoint).Port.ToString();
458 return "no connection";
464 internal Connection(ConnectionGroup connectionGroup) : base(null) {
466 // add this Connection to the pool in the connection group,
467 // keep a weak reference to it
469 m_MaximumUnauthorizedUploadLength = SettingsSectionInternal.Section.MaximumUnauthorizedUploadLength;
470 if(m_MaximumUnauthorizedUploadLength > 0){
471 m_MaximumUnauthorizedUploadLength*=1024;
473 m_ResponseData = new CoreResponseData();
474 m_ConnectionGroup = connectionGroup;
475 m_ReadBuffer = s_PinnableBufferCache.AllocateBuffer();
476 m_ReadBufferFromPinnableCache = true;
477 m_ReadState = ReadState.Start;
478 m_WaitList = new List<WaitListItem>();
479 m_WriteList = new ArrayList();
480 m_AbortDelegate = new HttpAbortDelegate(AbortOrDisassociate);
481 m_ConnectionUnlock = new UnlockConnectionDelegate(UnlockRequest);
483 // for status line parsing
484 m_StatusLineValues = new StatusLineValues();
485 m_RecycleTimer = ConnectionGroup.ServicePoint.ConnectionLeaseTimerQueue.CreateTimer();
486 // the following line must be the last line of the constructor
487 ConnectionGroup.Associate(this);
490 m_Error = WebExceptionStatus.Success;
491 if (PinnableBufferCacheEventSource.Log.IsEnabled())
493 PinnableBufferCacheEventSource.Log.DebugMessage1("CTOR: In System.Net.Connection.Connnection", this.GetHashCode());
498 if (m_ReadBufferFromPinnableCache)
500 if (PinnableBufferCacheEventSource.Log.IsEnabled())
502 PinnableBufferCacheEventSource.Log.DebugMessage1("DTOR: ERROR Needing to Free m_ReadBuffer in Connection Destructor", m_ReadBuffer.GetHashCode());
508 // If the buffer came from the the pinnable cache, return it to the cache.
509 // NOTE: This method is called from this object's finalizer and should not access any member objects.
510 void FreeReadBuffer() {
511 if (m_ReadBufferFromPinnableCache) {
512 s_PinnableBufferCache.FreeBuffer(m_ReadBuffer);
513 m_ReadBufferFromPinnableCache = false;
518 protected override void Dispose(bool disposing) {
519 if (PinnableBufferCacheEventSource.Log.IsEnabled()) {
520 PinnableBufferCacheEventSource.Log.DebugMessage1("In System.Net.Connection.Dispose()", this.GetHashCode());
523 base.Dispose(disposing);
526 internal int BusyCount {
528 return (m_ReadDone?0:1) + 2 * (m_WaitList.Count + m_WriteList.Count) + m_ReservedCount;
532 internal int IISVersion{
538 internal bool AtLeastOneResponseReceived {
540 return m_AtLeastOneResponseReceived;
546 SubmitRequest - Submit a request for sending.
548 The core submit handler. This is called when a request needs to be
549 submitted to the network. This routine is asynchronous; the caller
550 passes in an HttpSubmitDelegate that we invoke when the caller
551 can use the underlying network. The delegate is invoked with the
552 stream that it can right to.
554 On the Sync path, we work by attempting to gain control of the Connection
555 for writing and reading. If some other thread is using the Connection,
556 We wait inside of a LazyAsyncResult until it is availble.
560 request - request that's being submitted.
561 SubmitDelegate - Delegate to be invoked.
562 forcedsubmit - Queue the request even if connection is going to close.
565 true when the request was correctly submitted
568 // userReqeustThread says whether we can post IO from this thread or not.
569 [SuppressMessage("Microsoft.Reliability","CA2002:DoNotLockOnObjectsWithWeakIdentity", Justification="Re-Baseline System violations from 3.5 SP1 due to added parameter")]
570 internal bool SubmitRequest(HttpWebRequest request, bool forcedsubmit)
572 GlobalLog.Enter("Connection#" + ValidationHelper.HashString(this) + "::SubmitRequest", "request#" + ValidationHelper.HashString(request));
573 GlobalLog.ThreadContract(ThreadKinds.Unknown, "Connection#" + ValidationHelper.HashString(this) + "::SubmitRequest");
574 GlobalLog.Print("Connection#" + ValidationHelper.HashString(this) + "::SubmitRequest() Free:" + m_Free.ToString() + " m_WaitList.Count:" + m_WaitList.Count.ToString());
576 TriState startRequestResult = TriState.Unspecified;
577 ConnectionReturnResult returnResult = null;
578 bool expiredIdleConnection = false;
580 // See if the connection is free, and if the underlying socket or
581 // stream is set up. If it is, we can assign this connection to the
582 // request right now. Otherwise we'll have to put this request on
583 // on the wait list until it its turn.
587 request.AbortDelegate = m_AbortDelegate;
591 // Note that request is not on the connection list yet and Abort() will push the response on the request
592 GlobalLog.Leave("Connection#" + ValidationHelper.HashString(this) + "::SubmitRequest - (Request was aborted before being submitted)", true);
593 UnlockIfNeeded(request);
597 // There is a race condition between FindConnection and PrepareCloseConnectionSocket
598 // Some request may already try to submit themselves while the connection is dying.
600 // Retry if that's the case
604 GlobalLog.Leave("Connection#" + ValidationHelper.HashString(this) + "::SubmitRequest", "false - can't be pooled");
605 UnlockIfNeeded(request);
610 // There is a race condition between SubmitRequest and FindConnection. A non keep-alive request may
611 // get submitted on this connection just after we check for it. So make sure that if we are queueing
612 // behind non keep-alive request then its a forced submit.
613 // Retry if that's not the case.
615 if (!forcedsubmit && NonKeepAliveRequestPipelined)
617 GlobalLog.Leave("Connection#" + ValidationHelper.HashString(this) + "::SubmitRequest", "false - behind non keep-alive request");
618 UnlockIfNeeded(request);
622 // See if our timer still matches the SerivcePoint. If not, get rid of it.
623 if (m_RecycleTimer.Duration != ServicePoint.ConnectionLeaseTimerQueue.Duration) {
624 m_RecycleTimer.Cancel();
625 m_RecycleTimer = ServicePoint.ConnectionLeaseTimerQueue.CreateTimer();
628 if (m_RecycleTimer.HasExpired) {
629 request.KeepAlive = false;
633 // If the connection has already been locked by another request, then
634 // we fail the submission on this Connection.
637 if (LockedRequest != null && LockedRequest != request) {
638 GlobalLog.Leave("Connection#" + ValidationHelper.HashString(this) + "::SubmitRequest", "false");
643 //free means no one in the wait list. We should only add a request
644 //if the request can pipeline, or pipelining isn't available
646 GlobalLog.Print("Connection#" + ValidationHelper.HashString(this) + "::SubmitRequest WriteDone:" + m_WriteDone.ToString() + ", ReadDone:" + m_ReadDone.ToString() + ", m_WriteList.Count:" + m_WriteList.Count.ToString());
649 // If this request is marked as non keep-alive, we should stop pipelining more requests on this
650 // connection. The keep-alive context is transfered to the connection from request only after we start
651 // receiving response for the request.
653 if (!forcedsubmit && !m_NonKeepAliveRequestPipelined) {
654 m_NonKeepAliveRequestPipelined = (!request.KeepAlive && !request.NtlmKeepAlive);
657 if (m_Free && m_WriteDone && !forcedsubmit && (m_WriteList.Count == 0 || (request.Pipelined && !request.HasEntityBody && m_CanPipeline && m_Pipelining && !m_IsPipelinePaused))) {
659 // Connection is free. Mark it as busy and see if underlying
661 GlobalLog.Print("Connection#" + ValidationHelper.HashString(this) + "::SubmitRequest - Free ");
664 // This codepath handles the case where the server has closed the Connection by
665 // returning false below: the request will be resubmitted on a different Connection.
666 startRequestResult = StartRequest(request, true);
667 if (startRequestResult == TriState.Unspecified)
669 expiredIdleConnection = true;
670 PrepareCloseConnectionSocket(ref returnResult);
671 // Hard Close the socket.
673 FreeReadBuffer(); // Do it after close completes to insure buffer not in use
677 m_WaitList.Add(new WaitListItem(request, NetworkingPerfCounters.GetTimestamp()));
678 NetworkingPerfCounters.Instance.Increment(NetworkingPerfCounterName.HttpWebRequestQueued);
679 GlobalLog.Print("Connection#" + ValidationHelper.HashString(this) + "::SubmitRequest - Request added to WaitList#"+ValidationHelper.HashString(request));
682 GlobalLog.Print("Connection#" + ValidationHelper.HashString(this) + "::SubmitRequest() MyLocalPort:" + MyLocalPort + " ERROR: adding HttpWebRequest#" + ValidationHelper.HashString(request) +" to tunnelling WaitList");
685 GlobalLog.Print("Connection#" + ValidationHelper.HashString(this) + "::SubmitRequest() MyLocalPort:" + MyLocalPort + " adding HttpWebRequest#" + ValidationHelper.HashString(request) +" to non-tunnelling WaitList m_WaitList.Count:" + m_WaitList.Count);
692 if (expiredIdleConnection)
694 GlobalLog.Leave("Connection#" + ValidationHelper.HashString(this) + "::SubmitRequest(), expired idle connection", false);
695 ConnectionReturnResult.SetResponses(returnResult);
699 GlobalLog.DebugAddRequest(request, this, 0);
700 if(Logging.On)Logging.Associate(Logging.Web, this, request);
702 if (startRequestResult != TriState.Unspecified) {
703 CompleteStartRequest(true, request, startRequestResult);
705 // On [....], we wait for the Connection to be come availble here,
708 object responseObject = request.ConnectionAsyncResult.InternalWaitForCompletion();
709 ConnectStream writeStream = responseObject as ConnectStream;
710 AsyncTriState triStateAsync = null;
711 if (writeStream == null)
712 triStateAsync = responseObject as AsyncTriState;
714 GlobalLog.Print("Connection#" + ValidationHelper.HashString(this) + "::SubmitRequest() Pipelining:"+m_Pipelining);
716 if (startRequestResult == TriState.Unspecified && triStateAsync != null) {
717 // May need to recreate Connection here (i.e. call Socket.Connect)
718 CompleteStartRequest(true, request, triStateAsync.Value);
720 else if (writeStream != null)
722 // return the Stream to the Request
723 request.SetRequestSubmitDone(writeStream);
726 else if (responseObject is Exception)
728 Exception exception = responseObject as Exception;
729 WebException webException = responseObject as WebException;
730 GlobalLog.Leave("Connection#" + ValidationHelper.HashString(this) + "::SubmitRequest (SYNC) - Error waiting for a connection: " + exception.Message,
731 "Status:" + (webException == null? exception.GetType().FullName: (webException.Status.ToString() + " Internal Status: " + webException.InternalStatus.ToString())));
737 GlobalLog.Leave("Connection#" + ValidationHelper.HashString(this) + "::SubmitRequest", true);
741 private void UnlockIfNeeded(HttpWebRequest request) {
742 if (LockedRequest == request) {
747 // Wrapper for TriState for marhshalling across Thread boundaries
748 private class AsyncTriState {
749 public TriState Value;
750 public AsyncTriState(TriState newValue) {
757 StartRequest - Start a request going.
759 Routine to start a request. Called when we know the connection is
760 free and we want to get a request going. This routine initializes
761 some state, adds the request to the write queue, and checks to
762 see whether or not the underlying connection is alive. If it's
763 not, it queues a request to get it going. If the connection
764 was alive we call the callback delegate of the request.
766 This routine MUST BE called with the critcal section held.
769 request - request that's being started.
770 canPollRead - whether the calling code handles
771 Unspecified due to the Connection
772 being closed by the server.
775 True if request was started, false otherwise.
779 private TriState StartRequest(HttpWebRequest request, bool canPollRead)
782 "Connection#" + ValidationHelper.HashString(this) +
784 "HttpWebRequest#" + ValidationHelper.HashString(request) +
785 " WriteDone:"+ m_WriteDone +
786 " ReadDone:" + m_ReadDone +
787 " WaitList:" + m_WaitList.Count +
788 " WriteList:" + m_WriteList.Count);
789 GlobalLog.ThreadContract(ThreadKinds.Unknown,
790 "Connection#" + ValidationHelper.HashString(this) +
793 if (m_WriteList.Count == 0)
795 // check if we consider connection timed out
796 if (ServicePoint.MaxIdleTime != -1 &&
797 m_IdleSinceUtc != DateTime.MinValue &&
799 TimeSpan.FromMilliseconds(ServicePoint.MaxIdleTime) <
802 // This idle keep-alive connection timed out.
804 "Connection#" + ValidationHelper.HashString(this) +
806 " Expired connection was idle for "
808 ((DateTime.UtcNow - m_IdleSinceUtc).TotalSeconds) +
809 " secs, request will be retried: #" +
810 ValidationHelper.HashString(request));
811 return TriState.Unspecified; // don't use it
812 } else if (canPollRead) {
813 // Not timed out from our perspective but...
814 // Check if remote has:
815 // 1) closed an idle connection (TCP FIN)
817 // 2) sent some errant data on an idle connection.
818 bool pollRead = PollRead();
821 "Connection#" + ValidationHelper.HashString(this) +
822 "::StartRequest() " +
823 "Idle connection remotely closed, " +
824 "request will be retried: #" +
825 ValidationHelper.HashString(request));
826 return TriState.Unspecified; // don't use it
831 TriState needReConnect = TriState.False;
832 // Starting a request means the connection is not idle anymore
833 m_IdleSinceUtc = DateTime.MinValue;
835 // Initialze state, and add the request to the write queue.
838 // Note that m_Pipelining shold be only set here but the sanity check is made by the caller
839 // means if the caller has found that it is safe to pipeline the below result must be true as well
841 if (!m_IsPipelinePaused)
842 m_IsPipelinePaused = m_WriteList.Count >= s_MaxPipelinedCount;
844 m_Pipelining = m_CanPipeline && request.Pipelined && (!request.HasEntityBody);
846 // start of write process, disable done-ness flag
847 GlobalLog.Print("Connection#" + ValidationHelper.HashString(this) + "::StartRequest() setting WriteDone:" + m_WriteDone.ToString() + " to false");
849 GlobalLog.Print("Connection#" + ValidationHelper.HashString(this) + "::StartRequest() m_WriteList adding HttpWebRequest#" + ValidationHelper.HashString(request));
850 m_WriteList.Add(request);
852 GlobalLog.Print(m_WriteList.Count+" requests queued");
855 // with no network stream around, we will have to create one, therefore, we can't have
856 // the possiblity to even have a DoneReading().
859 needReConnect = TriState.True;
862 if (request.IsTunnelRequest) {
863 GlobalLog.Print("Connection#" + ValidationHelper.HashString(this) + "::StartRequest() MyLocalPort:" + MyLocalPort + " setting Tunnelling to true HttpWebRequest#" + ValidationHelper.HashString(request));
867 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));
871 GlobalLog.Leave("Connection#" + ValidationHelper.HashString(this) + "::StartRequest", needReConnect.ToString());
872 return needReConnect;
875 private void CompleteStartRequest(bool onSubmitThread, HttpWebRequest request, TriState needReConnect) {
876 GlobalLog.Enter("Connection#" + ValidationHelper.HashString(this) + "::CompleteStartRequest", ValidationHelper.HashString(request));
877 GlobalLog.ThreadContract(ThreadKinds.Unknown, "Connection#" + ValidationHelper.HashString(this) + "::CompleteStartRequest");
879 if (needReConnect == TriState.True) {
880 // Socket is not alive.
882 GlobalLog.Print("Connection#" + ValidationHelper.HashString(this) + "::CompleteStartRequest() Queue StartConnection Delegate ");
885 CompleteStartConnection(true, request);
887 else if (onSubmitThread) {
888 CompleteStartConnection(false, request);
890 // else - fall through and wake up other thread
892 catch (Exception exception) {
893 GlobalLog.Print("Connection#" + ValidationHelper.HashString(this) + "::CompleteStartRequest(): exception: " + exception.ToString());
894 if (NclUtilities.IsFatal(exception)) throw;
896 // Should not be here because CompleteStartConnection and below tries to catch everything
898 GlobalLog.Assert(exception.ToString());
901 // If neeeded wake up other thread where SubmitRequest was called
902 if (!request.Async) {
903 GlobalLog.Print("Connection#" + ValidationHelper.HashString(this) + "::CompleteStartRequest() Invoking Async Result");
904 request.ConnectionAsyncResult.InvokeCallback(new AsyncTriState(needReConnect));
908 GlobalLog.Leave("Connection#" + ValidationHelper.HashString(this) + "::CompleteStartRequest", "needReConnect");
914 // From now on the request.SetRequestSubmitDone must be called or it may hang
915 // For a [....] request the write side reponse windowwas opened in HttpWebRequest.SubmitRequest
917 request.OpenWriteSideResponseWindow();
920 ConnectStream writeStream = new ConnectStream(this, request);
922 // Call the request to let them know that we have a write-stream, this might invoke Send() call
923 if (request.Async || onSubmitThread) {
924 request.SetRequestSubmitDone(writeStream);
927 GlobalLog.Print("Connection#" + ValidationHelper.HashString(this) + "::CompleteStartRequest() Invoking Async Result");
928 request.ConnectionAsyncResult.InvokeCallback(writeStream);
930 GlobalLog.Leave("Connection#" + ValidationHelper.HashString(this) + "::CompleteStartRequest");
937 Gets the next request from the wait queue, if there is one.
939 Must be called with the crit sec held.
943 private HttpWebRequest CheckNextRequest()
945 GlobalLog.ThreadContract(ThreadKinds.Unknown, "Connection#" + ValidationHelper.HashString(this) + "::CheckNextRequest");
947 if (m_WaitList.Count == 0) {
948 // We're free now, if we're not going to close the connection soon.
949 m_Free = m_KeepAlive;
956 WaitListItem item = m_WaitList[0];
957 HttpWebRequest nextRequest = item.Request;
959 if (m_IsPipelinePaused)
960 m_IsPipelinePaused = m_WriteList.Count > s_MinPipelinedCount;
962 if (!nextRequest.Pipelined || nextRequest.HasEntityBody || !m_CanPipeline || !m_Pipelining || m_IsPipelinePaused) {
963 if (m_WriteList.Count != 0) {
967 if (nextRequest != null) {
968 GlobalLog.Print("Connection#" + ValidationHelper.HashString(this) + "::CheckNextRequest() Removing request#" + ValidationHelper.HashString(nextRequest) + " from m_WaitList. New Count:" + (m_WaitList.Count - 1).ToString());
970 NetworkingPerfCounters.Instance.IncrementAverage(NetworkingPerfCounterName.HttpWebRequestAvgQueueTime,
971 item.QueueStartTime);
972 m_WaitList.RemoveAt(0);
978 private void CompleteStartConnection(bool async, HttpWebRequest httpWebRequest)
980 GlobalLog.Enter("Connection#" + ValidationHelper.HashString(this) + "::CompleteStartConnection", "async:" + async.ToString() + " httpWebRequest:" + ValidationHelper.HashString(httpWebRequest));
981 GlobalLog.ThreadContract(ThreadKinds.Unknown, "Connection#" + ValidationHelper.HashString(this) + "::CompleteStartConnection");
983 WebExceptionStatus ws = WebExceptionStatus.ConnectFailure;
984 m_InnerException = null;
991 // m_WriteList can be empty if request got aborted. In that case no new requests can come in so it should remain zero.
992 if (m_WriteList.Count != 0)
994 GlobalLog.Assert(m_WriteList.Count == 1, "Connection#{0}::CompleteStartConnection()|WriteList is not sized 1.", ValidationHelper.HashString(this));
995 GlobalLog.Assert((m_WriteList[0] as HttpWebRequest) == httpWebRequest, "Connection#{0}::CompleteStartConnection()|Last request on write list does not match.", ValidationHelper.HashString(this));
1001 // we will create a tunnel through a proxy then create
1002 // and connect the socket we will use for the connection
1003 // otherwise we will just create a socket and use it
1005 if ((httpWebRequest.IsWebSocketRequest || httpWebRequest.Address.Scheme == Uri.UriSchemeHttps) &&
1006 ServicePoint.InternalProxyServicePoint)
1008 if(!TunnelThroughProxy(ServicePoint.InternalAddress, httpWebRequest,async)) {
1009 ws = WebExceptionStatus.ConnectFailure;
1012 if (async && success) {
1016 if (!Activate(httpWebRequest, async, new GeneralAsyncDelegate(CompleteConnectionWrapper)))
1022 catch (Exception exception) {
1023 if (m_InnerException == null)
1024 m_InnerException = exception;
1026 if (exception is WebException) {
1027 ws = ((WebException)exception).Status;
1033 ConnectionReturnResult returnResult = null;
1034 HandleError(false, false, ws, ref returnResult);
1035 ConnectionReturnResult.SetResponses(returnResult);
1036 GlobalLog.Leave("Connection#" + ValidationHelper.HashString(this) + "::CompleteStartConnection Failed to connect.");
1040 // Getting here means we connected synchronously. Continue with the next step.
1042 CompleteConnection(async, httpWebRequest);
1043 GlobalLog.Leave("Connection#" + ValidationHelper.HashString(this) + "::CompleteStartConnection");
1046 private void CompleteConnectionWrapper(object request, object state)
1049 using (GlobalLog.SetThreadKind(ThreadKinds.System | ThreadKinds.Async)) {
1051 GlobalLog.Enter("Connection#" + ValidationHelper.HashString(state) + "::CompleteConnectionWrapper", "request:" + ValidationHelper.HashString(request));
1053 Exception stateException = state as Exception;
1054 if (stateException != null)
1056 GlobalLog.Print("CompleteConnectionWrapper() Request#" + ValidationHelper.HashString(request) + " Connection is in error: " + stateException.ToString());
1057 ConnectionReturnResult returnResult = null;
1059 if (m_InnerException == null)
1060 m_InnerException = stateException;
1062 HandleError(false, false, WebExceptionStatus.ConnectFailure, ref returnResult);
1063 ConnectionReturnResult.SetResponses(returnResult);
1065 CompleteConnection(true, (HttpWebRequest) request);
1067 GlobalLog.Leave("Connection#" + ValidationHelper.HashString(state) + "::CompleteConnectionWrapper" + (stateException == null? string.Empty: " failed"));
1073 private void CompleteConnection(bool async, HttpWebRequest request)
1075 GlobalLog.Enter("Connection#" + ValidationHelper.HashString(this) + "::CompleteConnection", "async:" + async.ToString() + " request:" + ValidationHelper.HashString(request));
1076 GlobalLog.ThreadContract(ThreadKinds.Unknown, "Connection#" + ValidationHelper.HashString(this) + "::CompleteConnection");
1078 WebExceptionStatus ws = WebExceptionStatus.ConnectFailure;
1080 // From now on the request.SetRequestSubmitDone must be called or it may hang
1081 // For a [....] request the write side reponse windowwas opened in HttpWebRequest.SubmitRequest
1083 request.OpenWriteSideResponseWindow();
1089 if (request.Address.Scheme == Uri.UriSchemeHttps) {
1090 TlsStream tlsStream = new TlsStream(request.GetRemoteResourceUri().IdnHost,
1091 NetworkStream, request.ClientCertificates, ServicePoint, request,
1092 request.Async ? request.GetConnectingContext().ContextCopy : null);
1093 NetworkStream = tlsStream;
1096 ws = WebExceptionStatus.Success;
1099 // The TLS stream could not be created. Close the current non-TLS stream immediately
1100 // to prevent any future use of it. Due to race conditions, the error handling will sometimes
1101 // try to write (flush) out some of the HTTP headers to the stream as it is closing down the failed
1102 // HttpWebRequest. This would cause plain text to go on the wire even though the stream should
1103 // have been TLS encrypted.
1104 NetworkStream.Close();
1109 // There is a ---- with Abort so TlsStream ctor may throw.
1110 // SetRequestSubmitDone will deal with this kind of errors.
1113 m_ReadState = ReadState.Start;
1116 request.SetRequestSubmitDone(new ConnectStream(this, request));
1119 catch (Exception exception)
1121 if (m_InnerException == null)
1122 m_InnerException = exception;
1123 WebException webException = exception as WebException;
1124 if (webException != null)
1126 ws = webException.Status;
1130 if (ws != WebExceptionStatus.Success)
1132 ConnectionReturnResult returnResult = null;
1133 HandleError(false, false, ws, ref returnResult);
1134 ConnectionReturnResult.SetResponses(returnResult);
1136 if (Logging.On) Logging.PrintError(Logging.Web, this, "CompleteConnection", "on error");
1137 GlobalLog.Leave("Connection#" + ValidationHelper.HashString(this) + "::CompleteConnection", "on error");
1141 GlobalLog.Leave("Connection#" + ValidationHelper.HashString(this) + "::CompleteConnection");
1145 private void InternalWriteStartNextRequest(HttpWebRequest request, ref bool calledCloseConnection, ref TriState startRequestResult, ref HttpWebRequest nextRequest, ref ConnectionReturnResult returnResult) {
1146 GlobalLog.ThreadContract(ThreadKinds.Unknown, "Connection#" + ValidationHelper.HashString(this) + "::InternalWriteStartNextRequest");
1150 GlobalLog.Print("Connection#" + ValidationHelper.HashString(this) + "::WriteStartNextRequest() setting WriteDone:" + m_WriteDone.ToString() + " to true");
1154 // If we're not doing keep alive, and the read on this connection
1155 // has already completed, now is the time to close the
1158 //need to wait for read to set the error
1159 if (!m_KeepAlive || m_Error != WebExceptionStatus.Success || !CanBePooled) {
1160 GlobalLog.Print("Connection#" + ValidationHelper.HashString(this) + "::WriteStartNextRequest() m_WriteList.Count:" + m_WriteList.Count);
1162 // We could be closing because of an unexpected keep-alive
1163 // failure, ie we pipelined a few requests and in the middle
1164 // the remote server stopped doing keep alive. In this
1165 // case m_Error could be success, which would be misleading.
1166 // So in that case we'll set it to connection closed.
1168 if (m_Error == WebExceptionStatus.Success) {
1169 // Only reason we could have gotten here is because
1170 // we're not keeping the connection alive.
1171 m_Error = WebExceptionStatus.KeepAliveFailure;
1174 // PrepareCloseConnectionSocket is called with the critical section
1175 // held. Note that we know since it's not a keep-alive
1176 // connection the read half wouldn't have posted a receive
1177 // for this connection, so it's OK to call PrepareCloseConnectionSocket now.
1178 PrepareCloseConnectionSocket(ref returnResult);
1179 calledCloseConnection = true;
1183 if (m_Error!=WebExceptionStatus.Success) {
1184 GlobalLog.Print("Connection#" + ValidationHelper.HashString(this) + "::WriteStartNextRequest() a Failure, m_Error = " + m_Error.ToString());
1189 // If we're pipelining, we get get the next request going
1190 // as soon as the write is done. Otherwise we have to wait
1191 // until both read and write are done.
1194 GlobalLog.Print("Connection#" + ValidationHelper.HashString(this) + "::WriteStartNextRequest() Non-Error m_WriteList.Count:" + m_WriteList.Count + " m_WaitList.Count:" + m_WaitList.Count);
1196 if (m_Pipelining || m_ReadDone)
1198 nextRequest = CheckNextRequest();
1200 if (nextRequest != null)
1202 // This codepath doesn't handle the case where the server has closed the Connection because we
1203 // just finished using it and didn't get a Connection: close header.
1204 startRequestResult = StartRequest(nextRequest, false);
1205 GlobalLog.Assert(startRequestResult != TriState.Unspecified, "WriteStartNextRequest got TriState.Unspecified from StartRequest, things are about to hang!");
1211 internal void WriteStartNextRequest(HttpWebRequest request, ref ConnectionReturnResult returnResult) {
1212 GlobalLog.Enter("Connection#" + ValidationHelper.HashString(this) + "::WriteStartNextRequest" + " WriteDone:" + m_WriteDone + " ReadDone:" + m_ReadDone + " WaitList:" + m_WaitList.Count + " WriteList:" + m_WriteList.Count);
1213 GlobalLog.ThreadContract(ThreadKinds.Unknown, "Connection#" + ValidationHelper.HashString(this) + "::WriteStartNextRequest");
1215 TriState startRequestResult = TriState.Unspecified;
1216 HttpWebRequest nextRequest = null;
1217 bool calledCloseConnection = false;
1219 InternalWriteStartNextRequest(request, ref calledCloseConnection, ref startRequestResult, ref nextRequest, ref returnResult);
1221 GlobalLog.Print("Connection#" + ValidationHelper.HashString(this) + "::WriteStartNextRequest: Pipelining:" + m_Pipelining + " nextRequest#"+ValidationHelper.HashString(nextRequest));
1223 if (!calledCloseConnection && startRequestResult != TriState.Unspecified)
1225 GlobalLog.Print("Connection#" + ValidationHelper.HashString(this) + "::WriteStartNextRequest calling CompleteStartRequest");
1226 CompleteStartRequest(false, nextRequest, startRequestResult);
1229 GlobalLog.Leave("Connection#" + ValidationHelper.HashString(this) + "::WriteStartNextRequest");
1233 internal void SetLeftoverBytes(byte[] buffer, int bufferOffset, int bufferCount)
1235 // The ConnectStream read past the response of its HTTP response (can happen in chunked scenarios).
1236 // Get the buffer containing bytes belonging to the next request and use them for the next request.
1238 if (bufferOffset > 0)
1240 // We need to move leftover bytes to the beginning of the buffer.
1241 Buffer.BlockCopy(buffer, bufferOffset, buffer, 0, bufferCount);
1244 // If we had to reallocate the buffer, we are going to clobber the one that was allocated from the pin friendly cache.
1246 if (m_ReadBuffer != buffer)
1248 // if m_ReadBuffer is from the pinnable cache, give it back
1250 m_ReadBuffer = buffer;
1254 m_BytesRead = bufferCount;
1259 ReadStartNextRequest
1261 This method is called by a stream interface when it's done reading.
1262 We might possible free up the connection for another request here.
1264 Called when we think we might need to start another request because
1268 internal void ReadStartNextRequest(WebRequest currentRequest, ref ConnectionReturnResult returnResult)
1270 GlobalLog.Enter("Connection#" + ValidationHelper.HashString(this) + "::ReadStartNextRequest" + " WriteDone:" + m_WriteDone + " ReadDone:" + m_ReadDone + " WaitList:" + m_WaitList.Count + " WriteList:" + m_WriteList.Count);
1271 GlobalLog.ThreadContract(ThreadKinds.Unknown, "Connection#" + ValidationHelper.HashString(this) + "::ReadStartNextRequest");
1273 HttpWebRequest nextRequest = null;
1274 TriState startRequestResult = TriState.Unspecified;
1275 bool calledCloseConnection = false;
1276 bool mustExit = false;
1278 // ReadStartNextRequest is called by ConnectStream.CallDone: This guarantees that the request
1279 // is done and the response (response stream) was closed. Remove the reservation for the request.
1280 int currentCount = Interlocked.Decrement(ref m_ReservedCount);
1281 GlobalLog.Assert(currentCount >= 0, "m_ReservedCount must not be < 0 when decremented.");
1285 if (m_WriteList.Count > 0 && (object)currentRequest == m_WriteList[0])
1287 // advance back to state 0
1288 m_ReadState = ReadState.Start;
1289 m_WriteList.RemoveAt(0);
1291 // Must reset ConnectStream here to prevent a leak through the stream of the last request on each connection.
1292 m_ResponseData.m_ConnectStream = null;
1294 GlobalLog.Print("Connection#" + ValidationHelper.HashString(this) + "::ReadStartNextRequest() Removed request#" + ValidationHelper.HashString(currentRequest) + " from m_WriteList. New m_WriteList.Count:" + m_WriteList.Count.ToString());
1298 GlobalLog.Print("Connection#" + ValidationHelper.HashString(this) + "::ReadStartNextRequest() The request#" + ValidationHelper.HashString(currentRequest) + " was disassociated so do nothing. m_WriteList.Count:" + m_WriteList.Count.ToString());
1303 // Since this is called after we're done reading the current
1304 // request, if we're not doing keepalive and we're done
1305 // writing we can close the connection now.
1310 // m_ReadDone==true is implied because we just finished a request but really the value must still be false here
1313 throw new InternalException(); // other requests may already started reading on this connection, need a QFE
1315 if (!m_KeepAlive || m_Error != WebExceptionStatus.Success || !CanBePooled)
1317 GlobalLog.Print("Connection#" + ValidationHelper.HashString(this) + "::ReadStartNextRequest() KeepAlive:" + m_KeepAlive + " WriteDone:" + m_WriteDone);
1318 // Finished one request and connection is closing.
1319 // We will not read from this connection so set readDone = true
1325 // We could be closing because of an unexpected keep-alive
1326 // failure, ie we pipelined a few requests and in the middle
1327 // the remote server stopped doing keep alive. In this
1328 // case m_Error could be success, which would be misleading.
1329 // So in that case we'll set it to KeepAliveFailure.
1331 if (m_Error == WebExceptionStatus.Success) {
1332 // Only reason we could have gotten here is because
1333 // we're not keeping the connection alive.
1334 m_Error = WebExceptionStatus.KeepAliveFailure;
1337 // PrepareCloseConnectionSocket has to be called with the critical section held.
1338 PrepareCloseConnectionSocket(ref returnResult);
1339 calledCloseConnection = true;
1345 // We try to sort out KeepAliveFailure thing (search by context)
1346 m_AtLeastOneResponseReceived = true;
1348 if (m_WriteList.Count != 0)
1350 // If a *pipelined* request that is being submitted has finished with the headers, post a receive
1351 nextRequest = m_WriteList[0] as HttpWebRequest;
1352 // If the active request has not finished its headers we can set m_ReadDone = true
1353 // and that will be changed when said request will call CheckStartReceive
1354 if (!nextRequest.HeadersCompleted)
1360 // If there are no requests left to write (means pipeline),
1361 // we can get the next request from wait list going now.
1366 // Sometime we get a response before completing the body in which case
1367 // we defer next request to WriteStartNextRequest
1370 nextRequest = CheckNextRequest();
1372 if (nextRequest != null )
1374 // We cannot have HeadersCompleted on the request that was not placed yet on the write list
1375 if(nextRequest.HeadersCompleted) // TODO: change to be Assert but only when stress got stable-stable
1376 throw new InternalException();
1378 // This codepath doesn't handle the case where the server has closed the
1379 // Connection because we just finished using it and didn't get a
1380 // Connection: close header.
1381 startRequestResult = StartRequest(nextRequest, false);
1382 GlobalLog.Assert(startRequestResult != TriState.Unspecified, "ReadStartNextRequest got TriState.Unspecified from StartRequest, things are about to hang!");
1386 //There are no other requests to process, so make connection avaliable for all
1398 //set result here to prevent nesting of readstartnextrequest.
1399 if(returnResult != null){
1400 ConnectionReturnResult.SetResponses(returnResult);
1404 if(!mustExit && !calledCloseConnection)
1406 if (startRequestResult != TriState.Unspecified)
1408 CompleteStartRequest(false, nextRequest, startRequestResult);
1410 else if (nextRequest != null)
1412 // Handling receive, note that is for pipelinning case only !
1413 if (!nextRequest.Async)
1415 nextRequest.ConnectionReaderAsyncResult.InvokeCallback();
1419 if (m_BytesScanned < m_BytesRead)
1421 GlobalLog.Print("Connection#" + ValidationHelper.HashString(this) + "::ReadStartNextRequest() Calling ReadComplete, bytes unparsed = " + (m_BytesRead - m_BytesScanned));
1422 ReadComplete(0, WebExceptionStatus.Success);
1424 else if (Thread.CurrentThread.IsThreadPoolThread)
1426 GlobalLog.Print("Connection#" + ValidationHelper.HashString(this) + "::ReadStartNextRequest() Calling PostReceive().");
1431 // Offload to the threadpool to protect against the case where one request's thread posts IO that another request
1432 // depends on, but the first thread dies in the mean time.
1433 GlobalLog.Print("Connection#" + ValidationHelper.HashString(this) + "::ReadStartNextRequest() ThreadPool.UnsafeQueueUserWorkItem(m_PostReceiveDelegate, this)");
1434 ThreadPool.UnsafeQueueUserWorkItem(m_PostReceiveDelegate, this);
1439 GlobalLog.Leave("Connection#" + ValidationHelper.HashString(this) + "::ReadStartNextRequest");
1442 internal void MarkAsReserved()
1444 // We use an interlock here rather than a lock() to avoid deadlocks in the following situation:
1445 // - ConnectionGroup is holding lock(obj1), calls into Connection which is waiting for lock(obj2)
1446 // - on another thread Connection is holding lock(obj2) and calls into ConnectionGroup which will wait
1448 int currentCount = Interlocked.Increment(ref m_ReservedCount);
1449 GlobalLog.Assert(currentCount > 0, "m_ReservedCount must not be less or equal zero after incrementing.");
1455 internal void CheckStartReceive(HttpWebRequest request)
1459 request.HeadersCompleted = true;
1460 if (m_WriteList.Count == 0)
1462 // aborted request, was already dispatched.
1463 // Note it could have been aborted softly if not the first one in the pipeline
1467 // Note we do NOT allow receive if pipelining and the passed request is not the first one on the write queue
1468 if (!m_ReadDone || m_WriteList[0] != (object)request)
1470 // ReadStartNextRequest should take care of these cases
1475 m_CurrentRequest = (HttpWebRequest)m_WriteList[0];
1480 GlobalLog.Print("Connection#" + ValidationHelper.HashString(this) + "::CheckStartReceive() SYNC request, calling ConnectionReaderAsyncResult.InvokeCallback()");
1481 request.ConnectionReaderAsyncResult.InvokeCallback();
1483 else if (m_BytesScanned < m_BytesRead)
1485 GlobalLog.Print("Connection#" + ValidationHelper.HashString(this) + "::CheckStartReceive() Calling ReadComplete, bytes unparsed = " + (m_BytesRead - m_BytesScanned));
1486 ReadComplete(0, WebExceptionStatus.Success);
1488 else if (Thread.CurrentThread.IsThreadPoolThread)
1490 GlobalLog.Print("Connection#" + ValidationHelper.HashString(this) + "::CheckStartReceive() Calling PostReceive().");
1495 // Offload to the threadpool to protect against the case where one request's thread posts IO that another request
1496 // depends on, but the first thread dies in the mean time.
1497 GlobalLog.Print("Connection#" + ValidationHelper.HashString(this) + "::CheckStartReceive() ThreadPool.UnsafeQueueUserWorkItem(m_PostReceiveDelegate, this)");
1498 ThreadPool.UnsafeQueueUserWorkItem(m_PostReceiveDelegate, this);
1504 Routine Description:
1506 Clears out common member vars used for Status Line parsing
1518 private void InitializeParseStatusLine() {
1519 m_StatusState = BeforeVersionNumbers;
1520 m_StatusLineValues.MajorVersion = 0;
1521 m_StatusLineValues.MinorVersion = 0;
1522 m_StatusLineValues.StatusCode = 0;
1523 m_StatusLineValues.StatusDescription = null;
1528 Routine Description:
1530 Performs status line parsing on incomming server responses
1534 statusLine - status line that we wish to parse
1535 statusLineLength - length of the array
1536 statusLineInts - array of ints contanes result
1537 statusDescription - string with discription
1538 statusStatus - state stored between parse attempts
1542 bool - Success true/false
1546 private const int BeforeVersionNumbers = 0;
1547 private const int MajorVersionNumber = 1;
1548 private const int MinorVersionNumber = 2;
1549 private const int StatusCodeNumber = 3;
1550 private const int AfterStatusCode = 4;
1551 private const int AfterCarriageReturn = 5;
1553 private const string BeforeVersionNumberBytes = "HTTP/";
1555 private DataParseStatus ParseStatusLine(
1557 int statusLineLength,
1558 ref int bytesParsed,
1559 ref int [] statusLineInts,
1560 ref string statusDescription,
1561 ref int statusState,
1562 ref WebParseError parseError) {
1563 GlobalLog.Enter("Connection#" + ValidationHelper.HashString(this) + "::ParseStatusLine", statusLineLength.ToString(NumberFormatInfo.InvariantInfo) + ", " + bytesParsed.ToString(NumberFormatInfo.InvariantInfo) +", " +statusState.ToString(NumberFormatInfo.InvariantInfo));
1564 GlobalLog.ThreadContract(ThreadKinds.Unknown, "Connection#" + ValidationHelper.HashString(this) + "::ParseStatusLine");
1565 GlobalLog.Assert((statusLineLength - bytesParsed) >= 0, "Connection#{0}::ParseStatusLine()|(statusLineLength - bytesParsed) < 0", ValidationHelper.HashString(this));
1566 //GlobalLog.Dump(statusLine, bytesParsed, statusLineLength);
1568 DataParseStatus parseStatus = DataParseStatus.Done;
1569 int statusLineSize = 0;
1570 int startIndexStatusDescription = -1;
1571 int lastUnSpaceIndex = 0;
1574 // While walking the Status Line looking for terminating \r\n,
1575 // we extract the Major.Minor Versions and Status Code in that order.
1576 // text and spaces will lie between/before/after the three numbers
1577 // but the idea is to remember which number we're calculating based on a numeric state
1578 // If all goes well the loop will churn out an array with the 3 numbers plugged in as DWORDs
1581 while ((bytesParsed < statusLineLength) && (statusLine[bytesParsed] != '\r') && (statusLine[bytesParsed] != '\n')) {
1583 // below should be wrapped in while (response[i] != ' ') to be more robust???
1584 switch (statusState) {
1585 case BeforeVersionNumbers:
1586 if (statusLine[bytesParsed] == '/') {
1587 //INET_ASSERT(statusState == BeforeVersionNumbers);
1588 statusState++; // = MajorVersionNumber
1590 else if (statusLine[bytesParsed] == ' ') {
1591 statusState = StatusCodeNumber;
1596 case MajorVersionNumber:
1598 if (statusLine[bytesParsed] == '.') {
1599 //INET_ASSERT(statusState == MajorVersionNumber);
1600 statusState++; // = MinorVersionNumber
1604 goto case MinorVersionNumber;
1606 case MinorVersionNumber:
1608 if (statusLine[bytesParsed] == ' ') {
1609 //INET_ASSERT(statusState == MinorVersionNumber);
1610 statusState++; // = StatusCodeNumber
1614 goto case StatusCodeNumber;
1616 case StatusCodeNumber:
1618 if (Char.IsDigit((char)statusLine[bytesParsed])) {
1619 int val = statusLine[bytesParsed] - '0';
1620 statusLineInts[statusState] = statusLineInts[statusState] * 10 + val;
1622 else if (statusLineInts[StatusCodeNumber] > 0) {
1624 // we eat spaces before status code is found,
1625 // once we have the status code we can go on to the next
1626 // state on the next non-digit. This is done
1627 // to cover cases with several spaces between version
1628 // and the status code number.
1631 statusState++; // = AfterStatusCode
1634 else if (!Char.IsWhiteSpace((char) statusLine[bytesParsed])) {
1635 statusLineInts[statusState] = (int)-1;
1640 case AfterStatusCode:
1641 if (statusLine[bytesParsed] != ' ') {
1642 lastUnSpaceIndex = bytesParsed;
1643 if (startIndexStatusDescription == -1) {
1644 startIndexStatusDescription = bytesParsed;
1651 if (m_MaximumResponseHeadersLength>=0 && ++m_TotalResponseHeadersLength>=m_MaximumResponseHeadersLength) {
1652 parseStatus = DataParseStatus.DataTooBig;
1657 statusLineSize = bytesParsed;
1659 // add to Description if already partialy parsed
1660 if (startIndexStatusDescription != -1) {
1661 statusDescription +=
1662 WebHeaderCollection.HeaderEncoding.GetString(
1664 startIndexStatusDescription,
1665 lastUnSpaceIndex - startIndexStatusDescription + 1 );
1668 if (bytesParsed == statusLineLength) {
1670 // response now points one past the end of the buffer. We may be looking
1673 // if we're at the end of the connection then the server sent us an
1674 // incorrectly formatted response. Probably an error.
1676 // Otherwise its a partial response. We need more
1678 parseStatus = DataParseStatus.NeedMoreData;
1680 // if we really hit the end of the response then update the amount of
1683 GlobalLog.Leave("Connection#" + ValidationHelper.HashString(this) + "::ParseStatusLine", parseStatus.ToString());
1687 while ((bytesParsed < statusLineLength)
1688 && ((statusLine[bytesParsed] == '\r') || (statusLine[bytesParsed] == ' '))) {
1690 if (m_MaximumResponseHeadersLength>=0 && ++m_TotalResponseHeadersLength>=m_MaximumResponseHeadersLength) {
1691 parseStatus = DataParseStatus.DataTooBig;
1696 if (bytesParsed == statusLineLength) {
1699 // hit end of buffer without finding LF
1702 parseStatus = DataParseStatus.NeedMoreData;
1706 else if (statusLine[bytesParsed] == '\n') {
1708 if (m_MaximumResponseHeadersLength>=0 && ++m_TotalResponseHeadersLength>=m_MaximumResponseHeadersLength) {
1709 parseStatus = DataParseStatus.DataTooBig;
1713 // if we found the empty line then we are done
1715 parseStatus = DataParseStatus.Done;
1720 // Now we have our parsed header to add to the array
1724 if (parseStatus == DataParseStatus.Done && statusState != AfterStatusCode) {
1725 // need to handle the case where we parse the StatusCode,
1726 // but didn't get a status Line, and there was no space afer it.
1727 if (statusState != StatusCodeNumber || statusLineInts[StatusCodeNumber] <= 0) {
1729 // we're done with the status line, if we didn't parse all the
1730 // numbers needed this is invalid protocol on the server
1732 parseStatus = DataParseStatus.Invalid;
1736 GlobalLog.Print("Connection#" + ValidationHelper.HashString(this) + "::ParseStatusLine() StatusCode:" + statusLineInts[StatusCodeNumber] + " MajorVersionNumber:" + statusLineInts[MajorVersionNumber] + " MinorVersionNumber:" + statusLineInts[MinorVersionNumber] + " StatusDescription:" + ValidationHelper.ToString(statusDescription));
1737 GlobalLog.Leave("Connection#" + ValidationHelper.HashString(this) + "::ParseStatusLine", parseStatus.ToString());
1739 if (parseStatus == DataParseStatus.Invalid) {
1740 parseError.Section = WebParseErrorSection.ResponseStatusLine;
1741 parseError.Code = WebParseErrorCode.Generic;
1747 // Must all start with a different first character.
1748 private static readonly string[] s_ShortcutStatusDescriptions = new string[] { "OK", "Continue", "Unauthorized" };
1751 // Updated version of ParseStatusLine() - secure and fast
1753 private static unsafe DataParseStatus ParseStatusLineStrict(
1755 int statusLineLength,
1756 ref int bytesParsed,
1757 ref int statusState,
1758 StatusLineValues statusLineValues,
1759 int maximumHeaderLength,
1760 ref int totalBytesParsed,
1761 ref WebParseError parseError)
1763 GlobalLog.Enter("Connection::ParseStatusLineStrict", statusLineLength.ToString(NumberFormatInfo.InvariantInfo) + ", " + bytesParsed.ToString(NumberFormatInfo.InvariantInfo) + ", " + statusState.ToString(NumberFormatInfo.InvariantInfo));
1764 GlobalLog.ThreadContract(ThreadKinds.Unknown, "Connection::ParseStatusLineStrict");
1765 GlobalLog.Assert((statusLineLength - bytesParsed) >= 0, "Connection::ParseStatusLineStrict()|(statusLineLength - bytesParsed) < 0");
1766 GlobalLog.Assert(maximumHeaderLength <= 0 || totalBytesParsed <= maximumHeaderLength, "Connection::ParseStatusLineStrict()|Headers already read exceeds limit.");
1768 // Remember where we started.
1769 int initialBytesParsed = bytesParsed;
1771 // Set up parsing status with what will happen if we exceed the buffer.
1772 DataParseStatus parseStatus = DataParseStatus.DataTooBig;
1773 int effectiveMax = maximumHeaderLength <= 0 ? int.MaxValue : (maximumHeaderLength - totalBytesParsed + bytesParsed);
1774 if (statusLineLength < effectiveMax)
1776 parseStatus = DataParseStatus.NeedMoreData;
1777 effectiveMax = statusLineLength;
1781 if (bytesParsed >= effectiveMax)
1784 fixed (byte* byteBuffer = statusLine)
1786 // Use this switch to jump midway into the action. They all fall through until the end of the buffer is reached or
1787 // the status line is fully parsed.
1788 switch (statusState)
1790 case BeforeVersionNumbers:
1791 // This takes advantage of the fact that this token must be the very first thing in the response.
1792 while (totalBytesParsed - initialBytesParsed + bytesParsed < BeforeVersionNumberBytes.Length)
1794 if ((byte)BeforeVersionNumberBytes[totalBytesParsed - initialBytesParsed + bytesParsed] != byteBuffer[bytesParsed])
1796 parseStatus = DataParseStatus.Invalid;
1800 if(++bytesParsed == effectiveMax)
1804 // When entering the MajorVersionNumber phase, make sure at least one digit is present.
1805 if (byteBuffer[bytesParsed] == '.')
1807 parseStatus = DataParseStatus.Invalid;
1811 statusState = MajorVersionNumber;
1812 goto case MajorVersionNumber;
1814 case MajorVersionNumber:
1815 while (byteBuffer[bytesParsed] != '.')
1817 if (byteBuffer[bytesParsed] < '0' || byteBuffer[bytesParsed] > '9')
1819 parseStatus = DataParseStatus.Invalid;
1823 statusLineValues.MajorVersion = statusLineValues.MajorVersion * 10 + byteBuffer[bytesParsed] - '0';
1825 if (++bytesParsed == effectiveMax)
1829 // Need visibility past the dot.
1830 if (bytesParsed + 1 == effectiveMax)
1834 // When entering the MinorVersionNumber phase, make sure at least one digit is present.
1835 if (byteBuffer[bytesParsed] == ' ')
1837 parseStatus = DataParseStatus.Invalid;
1841 statusState = MinorVersionNumber;
1842 goto case MinorVersionNumber;
1844 case MinorVersionNumber:
1845 // Only a single SP character is allowed to delimit fields in the status line.
1846 while (byteBuffer[bytesParsed] != ' ')
1848 if (byteBuffer[bytesParsed] < '0' || byteBuffer[bytesParsed] > '9')
1850 parseStatus = DataParseStatus.Invalid;
1854 statusLineValues.MinorVersion = statusLineValues.MinorVersion * 10 + byteBuffer[bytesParsed] - '0';
1856 if (++bytesParsed == effectiveMax)
1860 statusState = StatusCodeNumber;
1862 // Start the status code out as "1". This will effectively add 1000 to the code. It's used to count
1863 // the number of digits to make sure it's three. At the end, subtract 1000.
1864 statusLineValues.StatusCode = 1;
1866 // Move past the space.
1867 if (++bytesParsed == effectiveMax)
1870 goto case StatusCodeNumber;
1872 case StatusCodeNumber:
1873 // RFC2616 says codes with an unrecognized first digit
1874 // should be rejected. We're allowing the application to define their own "understanding" of
1875 // 0, 6, 7, 8, and 9xx codes.
1876 while (byteBuffer[bytesParsed] >= '0' && byteBuffer[bytesParsed] <= '9')
1878 // Make sure it isn't too big. The leading '1' will be removed after three digits are read.
1879 if (statusLineValues.StatusCode >= 1000)
1881 parseStatus = DataParseStatus.Invalid;
1885 statusLineValues.StatusCode = statusLineValues.StatusCode * 10 + byteBuffer[bytesParsed] - '0';
1887 if (++bytesParsed == effectiveMax)
1891 // Make sure there was enough, and exactly one space.
1892 if (byteBuffer[bytesParsed] != ' ' || statusLineValues.StatusCode < 1000)
1894 if(byteBuffer[bytesParsed] == '\r' && statusLineValues.StatusCode >= 1000){
1895 statusLineValues.StatusCode -= 1000;
1896 statusState = AfterCarriageReturn;
1897 if (++bytesParsed == effectiveMax)
1899 goto case AfterCarriageReturn;
1901 parseStatus = DataParseStatus.Invalid;
1905 // Remove the extra leading 1.
1906 statusLineValues.StatusCode -= 1000;
1908 statusState = AfterStatusCode;
1910 // Move past the space.
1911 if (++bytesParsed == effectiveMax)
1914 goto case AfterStatusCode;
1916 case AfterStatusCode:
1918 // Check for shortcuts.
1919 if (statusLineValues.StatusDescription == null)
1921 foreach (string s in s_ShortcutStatusDescriptions)
1923 if (bytesParsed < effectiveMax - s.Length && byteBuffer[bytesParsed] == (byte) s[0])
1926 byte *pBuffer = byteBuffer + bytesParsed + 1;
1927 for(i = 1; i < s.Length; i++)
1928 if (*(pBuffer++) != (byte) s[i])
1932 statusLineValues.StatusDescription = s;
1933 bytesParsed += s.Length;
1940 int beginning = bytesParsed;
1942 while (byteBuffer[bytesParsed] != '\r')
1944 if (byteBuffer[bytesParsed] < ' ' || byteBuffer[bytesParsed] == 127)
1946 parseStatus = DataParseStatus.Invalid;
1950 if (++bytesParsed == effectiveMax)
1952 string s = WebHeaderCollection.HeaderEncoding.GetString(byteBuffer + beginning, bytesParsed - beginning);
1953 if (statusLineValues.StatusDescription == null)
1954 statusLineValues.StatusDescription = s;
1956 statusLineValues.StatusDescription += s;
1962 if (bytesParsed > beginning)
1964 string s = WebHeaderCollection.HeaderEncoding.GetString(byteBuffer + beginning, bytesParsed - beginning);
1965 if (statusLineValues.StatusDescription == null)
1966 statusLineValues.StatusDescription = s;
1968 statusLineValues.StatusDescription += s;
1970 else if (statusLineValues.StatusDescription == null)
1972 statusLineValues.StatusDescription = "";
1975 statusState = AfterCarriageReturn;
1977 // Move past the CR.
1978 if (++bytesParsed == effectiveMax)
1981 goto case AfterCarriageReturn;
1984 case AfterCarriageReturn:
1985 if (byteBuffer[bytesParsed] != '\n')
1987 parseStatus = DataParseStatus.Invalid;
1991 parseStatus = DataParseStatus.Done;
1998 totalBytesParsed += bytesParsed - initialBytesParsed;
2000 GlobalLog.Print("Connection::ParseStatusLineStrict() StatusCode:" + statusLineValues.StatusCode + " MajorVersionNumber:" + statusLineValues.MajorVersion + " MinorVersionNumber:" + statusLineValues.MinorVersion + " StatusDescription:" + ValidationHelper.ToString(statusLineValues.StatusDescription));
2001 GlobalLog.Leave("Connection::ParseStatusLineStrict", parseStatus.ToString());
2003 if (parseStatus == DataParseStatus.Invalid) {
2004 parseError.Section = WebParseErrorSection.ResponseStatusLine;
2005 parseError.Code = WebParseErrorCode.Generic;
2014 Routine Description:
2016 SetStatusLineParsed - processes the result of status line,
2017 after it has been parsed, reads vars and formats result of parsing
2021 None - uses member vars
2029 private void SetStatusLineParsed() {
2030 // transfer this to response data
2031 m_ResponseData.m_StatusCode = (HttpStatusCode) m_StatusLineValues.StatusCode;
2032 m_ResponseData.m_StatusDescription = m_StatusLineValues.StatusDescription;
2033 m_ResponseData.m_IsVersionHttp11 = m_StatusLineValues.MajorVersion >= 1 && m_StatusLineValues.MinorVersion >= 1;
2034 if (ServicePoint.HttpBehaviour==HttpBehaviour.Unknown || ServicePoint.HttpBehaviour==HttpBehaviour.HTTP11 && !m_ResponseData.m_IsVersionHttp11) {
2035 // it's only safe to start doing HTTP/1.1 behaviour if the server's version was unknown
2036 // or if we need to downgrade
2037 ServicePoint.HttpBehaviour = m_ResponseData.m_IsVersionHttp11 ? HttpBehaviour.HTTP11 : HttpBehaviour.HTTP10;
2040 m_CanPipeline = ServicePoint.SupportsPipelining;
2045 ProcessHeaderData - Pulls out Content-length, and other critical
2046 data from the newly parsed headers
2054 long - size of contentLength that we are to use
2057 private long ProcessHeaderData(ref bool fHaveChunked, HttpWebRequest request, out bool dummyResponseStream)
2059 long contentLength = -1;
2060 fHaveChunked = false;
2062 // Check for the "Transfer-Encoding" header to contain the "chunked" string
2064 string transferEncodingString = m_ResponseData.m_ResponseHeaders[HttpKnownHeaderNames.TransferEncoding];
2065 if (transferEncodingString!=null) {
2066 transferEncodingString = transferEncodingString.ToLower(CultureInfo.InvariantCulture);
2067 fHaveChunked = transferEncodingString.IndexOf(HttpWebRequest.ChunkedHeader) != -1;
2070 if (!fHaveChunked) {
2072 // If the response is not chunked, parse the "Content-Length" into a long for data size.
2074 string contentLengthString = m_ResponseData.m_ResponseHeaders.ContentLength;
2075 if (contentLengthString!=null) {
2076 int index = contentLengthString.IndexOf(':');
2078 contentLengthString = contentLengthString.Substring(index + 1);
2080 bool success = long.TryParse(contentLengthString, NumberStyles.None, CultureInfo.InvariantCulture.NumberFormat, out contentLength);
2083 // in some very rare cases, a proxy server may
2084 // send us a pair of numbers in comma delimated
2085 // fashion, so we need to handle this case
2086 index = contentLengthString.LastIndexOf(',');
2088 contentLengthString = contentLengthString.Substring(index + 1);
2089 success = long.TryParse(contentLengthString, NumberStyles.None, CultureInfo.InvariantCulture.NumberFormat, out contentLength);
2095 if (contentLength < 0)
2097 GlobalLog.Leave("Connection#" + ValidationHelper.HashString(this) + "::ProcessHeaderData - ContentLength value in header: " + contentLengthString + ", HttpWebRequest#"+ValidationHelper.HashString(m_CurrentRequest));
2098 contentLength = c_InvalidContentLength; // This will indicate a CL error to the caller
2103 // ** else ** signal no content-length present??? or error out?
2104 GlobalLog.Print("Connection#" + ValidationHelper.HashString(this) + "::ProcessHeaderData() Content-Length parsed:" + contentLength.ToString(NumberFormatInfo.InvariantInfo));
2106 dummyResponseStream = !request.CanGetResponseStream || m_ResponseData.m_StatusCode < HttpStatusCode.OK ||
2107 m_ResponseData.m_StatusCode == HttpStatusCode.NoContent || (m_ResponseData.m_StatusCode == HttpStatusCode.NotModified && contentLength < 0) ;
2112 // Deciding on KEEP ALIVE
2114 bool resetKeepAlive = false;
2116 //(1) if no content-length and no chunked, then turn off keep-alive
2117 // In some cases, though, Content-Length should be assumed to be 0 based on HTTP RFC 2616
2118 if (!dummyResponseStream && contentLength < 0 && !fHaveChunked)
2120 resetKeepAlive = true;
2122 //(2) A workaround for a failed client ssl session on IIS6
2123 // The problem is that we cannot change the connection group name after it gets created.
2124 // IIS6 does not close the connection on 403 so all subsequent requests will fail to be authorized on THAT connection.
2125 //-----------------------------------------------------------------------------------------------
2128 //The DTS Issue 595216 claims that we are unnecessarily closing the
2129 //connection on 403 - even if it is a non SSL request. It seems
2130 //that the original intention is to close the request for SSL requests
2131 //The following code change would enforce closing onl fo SSL requests.
2132 //-----------------------------------------------------------------------------------------------
2133 else if (m_ResponseData.m_StatusCode == HttpStatusCode.Forbidden && NetworkStream is TlsStream)
2135 resetKeepAlive = true;
2137 // (3) Possibly cease posting a big body on the connection, was invented mainly for the very first 401 response
2139 // This optimization is for the discovery legs only. For ntlm this is fine, because the 1st actual authleg
2140 // is always sent w/ content-length = 0.
2141 // For Kerberos preauth, it there could be 1 or 2 auth legs, but we don't know how many there are in advance,
2142 // so we don't have a way of eliminating the 1st auth leg.
2143 else if (m_ResponseData.m_StatusCode > HttpWebRequest.MaxOkStatus &&
2144 ((request.CurrentMethod == KnownHttpVerb.Post || request.CurrentMethod == KnownHttpVerb.Put) &&
2145 m_MaximumUnauthorizedUploadLength >= 0 && request.ContentLength > m_MaximumUnauthorizedUploadLength
2146 && (request.CurrentAuthenticationState == null || request.CurrentAuthenticationState.Module == null)))
2148 resetKeepAlive = true;
2150 //(4) for Http/1.0 servers, we can't be sure what their behavior
2151 // in this case, so the best thing is to disable KeepAlive unless explicitly set
2157 //in v2.0, in case of SSL Requests through proxy that require NTLM authentication,
2158 //we are not honoring the Proxy-Connection: Keep-Alive header and
2159 //closing the connection.
2161 //In v1.1 we did not have this issue because in v1.1, we would have set an
2162 //EmptyProxy on the CONNECT request which kind of made it look like the
2163 //service point is a proxy service point
2165 //In v2.0, we don't use the GlobalProxySelection.GetEmptyWebProxy we use null
2166 //to indicate we are not using a proxy.
2167 //The CONNECT request is a proxy request and the service point is to the
2171 //This is a surgical fix. The "UsesProxySemantics is defined as
2172 //ServicePoint is a Proxy Service point && (scheme is != https || the request is a tunnel request)
2173 //Ideally we use one definition of whether we are going trough a proxy or not.
2174 //The fact is that if you are connecting to a proxy, it is a proxy request and
2175 //you should honor the Proxy-Connection header.
2177 //For the purpose of this QFE, when we receive a header we test
2178 //if this is a Proxy Service Point OR if this is a TUNNEL request
2182 bool haveClose = false;
2183 bool haveKeepAlive = false;
2184 string connection = m_ResponseData.m_ResponseHeaders[HttpKnownHeaderNames.Connection];
2185 if (connection == null && (
2186 (ServicePoint.InternalProxyServicePoint) ||
2187 (request.IsTunnelRequest)))
2189 connection = m_ResponseData.m_ResponseHeaders[HttpKnownHeaderNames.ProxyConnection];
2192 if (connection != null) {
2193 connection = connection.ToLower(CultureInfo.InvariantCulture);
2194 if (connection.IndexOf("keep-alive") != -1) {
2195 haveKeepAlive = true;
2197 else if (connection.IndexOf("close") != -1) {
2202 if ((haveClose && ServicePoint.HttpBehaviour==HttpBehaviour.HTTP11) ||
2203 (!haveKeepAlive && ServicePoint.HttpBehaviour<=HttpBehaviour.HTTP10))
2205 resetKeepAlive = true;
2213 m_KeepAlive = false;
2219 return contentLength;
2222 internal bool KeepAlive
2230 internal bool NonKeepAliveRequestPipelined
2234 return m_NonKeepAliveRequestPipelined;
2242 Handles parsing of the blocks of data received after buffer,
2243 distributes the data to stream constructors as needed
2245 returnResult - contains a object containing Requests
2246 that must be notified upon return from callback
2249 private DataParseStatus ParseStreamData(ref ConnectionReturnResult returnResult)
2251 GlobalLog.Enter("Connection#" + ValidationHelper.HashString(this) + "::ParseStreamData");
2253 if (m_CurrentRequest == null)
2255 GlobalLog.Leave("Connection#" + ValidationHelper.HashString(this) + "::ParseStreamData - Aborted Request, return DataParseStatus.Invalid");
2256 m_ParseError.Section = WebParseErrorSection.Generic;
2257 m_ParseError.Code = WebParseErrorCode.UnexpectedServerResponse;
2258 return DataParseStatus.Invalid;
2261 bool fHaveChunked = false;
2262 bool dummyResponseStream;
2263 // content-length if there is one
2264 long contentLength = ProcessHeaderData(ref fHaveChunked, m_CurrentRequest, out dummyResponseStream);
2266 GlobalLog.Assert(!fHaveChunked || contentLength == -1, "Connection#{0}::ParseStreamData()|fHaveChunked but contentLength != -1", ValidationHelper.HashString(this));
2268 if (contentLength == c_InvalidContentLength)
2270 m_ParseError.Section = WebParseErrorSection.ResponseHeader;
2271 m_ParseError.Code = WebParseErrorCode.InvalidContentLength;
2272 return DataParseStatus.Invalid;
2275 // bytes left over that have not been parsed
2276 int bufferLeft = (m_BytesRead - m_BytesScanned);
2278 if (m_ResponseData.m_StatusCode > HttpWebRequest.MaxOkStatus)
2280 // This will tell the request to be prepared for possible connection drop
2281 // Also that will stop writing on the wire if the connection is not kept alive
2282 m_CurrentRequest.ErrorStatusCodeNotify(this, m_KeepAlive, false);
2287 // If pipelining, then look for extra data that could
2288 // be part of of another stream, if its there,
2289 // then we need to copy it, add it to a stream,
2290 // and then continue with the next headers
2293 if (dummyResponseStream)
2296 fHaveChunked = false;
2302 if (!fHaveChunked && (contentLength <= (long)Int32.MaxValue))
2304 bytesToCopy = (int)contentLength;
2308 DataParseStatus result;
2310 GlobalLog.Print("Connection#" + ValidationHelper.HashString(this) + "::ParseStreamData() bytesToCopy:" + bytesToCopy + " bufferLeft:" + bufferLeft);
2312 if (m_CurrentRequest.IsWebSocketRequest && m_ResponseData.m_StatusCode == HttpStatusCode.SwitchingProtocols)
2314 m_ResponseData.m_ConnectStream = new ConnectStream(this, m_ReadBuffer, m_BytesScanned, bufferLeft, bufferLeft, fHaveChunked, m_CurrentRequest);
2316 // The parsing will be resumed from m_BytesScanned when response stream is closed.
2317 result = DataParseStatus.Done;
2320 else if (bytesToCopy != -1 && bytesToCopy <= bufferLeft)
2322 m_ResponseData.m_ConnectStream = new ConnectStream(this, m_ReadBuffer, m_BytesScanned, bytesToCopy, dummyResponseStream? 0: contentLength, fHaveChunked, m_CurrentRequest);
2324 // The parsing will be resumed from m_BytesScanned when response stream is closed.
2325 result = DataParseStatus.ContinueParsing;
2326 m_BytesScanned += bytesToCopy;
2330 m_ResponseData.m_ConnectStream = new ConnectStream(this, m_ReadBuffer, m_BytesScanned, bufferLeft, dummyResponseStream? 0: contentLength, fHaveChunked, m_CurrentRequest);
2332 // This is the default case where we have a buffer with no more streams except the last one to create so we create it.
2333 // Note the buffer is fully consumed so we can reset the buffer offests.
2334 result = DataParseStatus.Done;
2338 m_ResponseData.m_ContentLength = contentLength;
2339 ConnectionReturnResult.Add(ref returnResult, m_CurrentRequest, m_ResponseData.Clone());
2342 GlobalLog.DebugUpdateRequest(m_CurrentRequest, this, GlobalLog.WaitingForReadDoneFlag);
2345 GlobalLog.Leave("Connection#" + ValidationHelper.HashString(this) + "::ParseStreamData");
2346 return result; // response stream is taking over the reading
2349 // Called before restarting Read operations
2350 private void ClearReaderState() {
2357 ParseResponseData - Parses the incomming headers, and handles
2358 creation of new streams that are found while parsing, and passes
2359 extra data the new streams
2363 returnResult - returns an object containing items that need to be called
2364 at the end of the read callback
2368 bool - true if one should continue reading more data
2371 private DataParseStatus ParseResponseData(ref ConnectionReturnResult returnResult, out bool requestDone, out CoreResponseData continueResponseData)
2373 GlobalLog.Enter("Connection#" + ValidationHelper.HashString(this) + "::ParseResponseData()");
2375 DataParseStatus parseStatus = DataParseStatus.NeedMoreData;
2376 DataParseStatus parseSubStatus;
2378 // Indicates whether or not at least one whole request was processed in this loop.
2379 // (i.e. Whether ParseStreamData() was called.
2380 requestDone = false;
2381 continueResponseData = null;
2383 // loop in case of multiple sets of headers or streams,
2384 // that may be generated due to a pipelined response
2386 // Invariants: at the start of this loop, m_BytesRead
2387 // is the number of bytes in the buffer, and m_BytesScanned
2388 // is how many bytes of the buffer we've consumed so far.
2389 // and the m_ReadState var will be updated at end of
2390 // each code path, call to this function to reflect,
2391 // the state, or error condition of the parsing of data
2393 // We use the following variables in the code below:
2395 // m_ReadState - tracks the current state of our Parsing in a
2397 // Start - initial start state and begining of response
2398 // StatusLine - the first line sent in response, include status code
2399 // Headers - \r\n delimiated Header parsing until we find entity body
2400 // Data - Entity Body parsing, if we have all data, we create stream directly
2402 // m_ResponseData - An object used to gather Stream, Headers, and other
2403 // tidbits so that a request/Response can receive this data when
2404 // this code is finished processing
2406 // m_ReadBuffer - Of course the buffer of data we are parsing from
2408 // m_BytesScanned - The bytes scanned in this buffer so far,
2409 // since its always assumed that parse to completion, this
2410 // var becomes ended of known data at the end of this function,
2413 // m_BytesRead - The total bytes read in buffer, should be const,
2414 // till its updated at end of function.
2418 // Now attempt to parse the data,
2419 // we first parse status line,
2420 // then read headers,
2421 // and finally transfer results to a new stream, and tell request
2424 switch (m_ReadState) {
2426 case ReadState.Start:
2429 if (m_CurrentRequest == null)
2433 if (m_WriteList.Count == 0 || ((m_CurrentRequest = m_WriteList[0] as HttpWebRequest) == null))
2435 m_ParseError.Section = WebParseErrorSection.Generic;
2436 m_ParseError.Code = WebParseErrorCode.Generic;
2437 parseStatus = DataParseStatus.Invalid;
2444 // Start of new response. Transfer the keep-alive context from the corresponding request to
2447 m_KeepAlive &= (m_CurrentRequest.KeepAlive || m_CurrentRequest.NtlmKeepAlive);
2449 m_MaximumResponseHeadersLength = m_CurrentRequest.MaximumResponseHeadersLength * 1024;
2450 m_ResponseData = new CoreResponseData();
2451 m_ReadState = ReadState.StatusLine;
2452 m_TotalResponseHeadersLength = 0;
2454 InitializeParseStatusLine();
2455 goto case ReadState.StatusLine;
2457 case ReadState.StatusLine:
2459 // Reads HTTP status response line
2461 if (SettingsSectionInternal.Section.UseUnsafeHeaderParsing)
2463 // This one uses an array to store the parsed values in. Marshal between this legacy way.
2464 int[] statusInts = new int[] { 0, m_StatusLineValues.MajorVersion, m_StatusLineValues.MinorVersion, m_StatusLineValues.StatusCode };
2465 if (m_StatusLineValues.StatusDescription == null)
2466 m_StatusLineValues.StatusDescription = "";
2468 parseSubStatus = ParseStatusLine(
2469 m_ReadBuffer, // buffer we're working with
2470 m_BytesRead, // total bytes read so far
2471 ref m_BytesScanned, // index off of what we've scanned
2473 ref m_StatusLineValues.StatusDescription,
2477 m_StatusLineValues.MajorVersion = statusInts[1];
2478 m_StatusLineValues.MinorVersion = statusInts[2];
2479 m_StatusLineValues.StatusCode = statusInts[3];
2483 parseSubStatus = ParseStatusLineStrict(
2484 m_ReadBuffer, // buffer we're working with
2485 m_BytesRead, // total bytes read so far
2486 ref m_BytesScanned, // index off of what we've scanned
2489 m_MaximumResponseHeadersLength,
2490 ref m_TotalResponseHeadersLength,
2494 if (parseSubStatus == DataParseStatus.Done)
2496 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));
2497 SetStatusLineParsed();
2498 m_ReadState = ReadState.Headers;
2499 m_ResponseData.m_ResponseHeaders = new WebHeaderCollection(WebHeaderCollectionType.HttpWebResponse);
2500 goto case ReadState.Headers;
2502 else if (parseSubStatus != DataParseStatus.NeedMoreData)
2505 // report error - either Invalid or DataTooBig
2507 GlobalLog.Print("Connection#" + ValidationHelper.HashString(this) + "::ParseResponseData() ParseStatusLine() parseSubStatus:" + parseSubStatus.ToString());
2508 parseStatus = parseSubStatus;
2512 break; // read more data
2514 case ReadState.Headers:
2516 // Parse additional lines of header-name: value pairs
2518 if (m_BytesScanned >= m_BytesRead) {
2520 // we already can tell we need more data
2525 if (SettingsSectionInternal.Section.UseUnsafeHeaderParsing)
2527 parseSubStatus = m_ResponseData.m_ResponseHeaders.ParseHeaders(
2531 ref m_TotalResponseHeadersLength,
2532 m_MaximumResponseHeadersLength,
2537 parseSubStatus = m_ResponseData.m_ResponseHeaders.ParseHeadersStrict(
2541 ref m_TotalResponseHeadersLength,
2542 m_MaximumResponseHeadersLength,
2546 if (parseSubStatus == DataParseStatus.Invalid || parseSubStatus == DataParseStatus.DataTooBig)
2551 GlobalLog.Print("Connection#" + ValidationHelper.HashString(this) + "::ParseResponseData() ParseHeaders() parseSubStatus:" + parseSubStatus.ToString());
2552 parseStatus = parseSubStatus;
2555 else if (parseSubStatus == DataParseStatus.Done)
2557 if (Logging.On) Logging.PrintInfo(Logging.Web, this, SR.GetString(SR.net_log_received_headers, m_ResponseData.m_ResponseHeaders.ToString(true)));
2559 GlobalLog.Print("Connection#" + ValidationHelper.HashString(this) + "::ParseResponseData() DataParseStatus.Done StatusCode:" + (int)m_ResponseData.m_StatusCode + " m_BytesRead:" + m_BytesRead + " m_BytesScanned:" + m_BytesScanned);
2561 //get the IIS server version
2562 if(m_IISVersion == -1){
2563 string server = m_ResponseData.m_ResponseHeaders.Server;
2564 if (server != null && server.ToLower(CultureInfo.InvariantCulture).Contains("microsoft-iis")){
2565 int i = server.IndexOf("/");
2566 if(i++>0 && i <server.Length){
2567 m_IISVersion = server[i++] - '0';
2568 while(i < server.Length && Char.IsDigit(server[i])) {
2569 m_IISVersion = m_IISVersion*10 + server[i++] - '0';
2573 //we got a response,so if we don't know the server by now and it wasn't a 100 continue,
2574 //we can't assume we will ever know it. IIS5 sends its server header w/ the continue
2576 if(m_IISVersion == -1 && m_ResponseData.m_StatusCode != HttpStatusCode.Continue){
2581 if (m_ResponseData.m_StatusCode == HttpStatusCode.Continue || m_ResponseData.m_StatusCode == HttpStatusCode.BadRequest) {
2583 GlobalLog.Assert(m_CurrentRequest != null, "Connection#{0}::ParseResponseData()|m_CurrentRequest == null", ValidationHelper.HashString(this));
2584 GlobalLog.Print("Connection#" + ValidationHelper.HashString(this) + "::ParseResponseData() HttpWebRequest#" + ValidationHelper.HashString(m_CurrentRequest));
2586 if (m_ResponseData.m_StatusCode == HttpStatusCode.BadRequest) {
2587 // If we have a 400 and we were sending a chunked request going through to a proxy with a chunked upload,
2588 // this proxy is a partially compliant so shut off fancy features (pipelining and chunked uploads)
2589 GlobalLog.Print("Connection#" + ValidationHelper.HashString(this) + "::ParseResponseData() got a 400 StatusDescription:" + m_ResponseData.m_StatusDescription);
2590 if (ServicePoint.HttpBehaviour == HttpBehaviour.HTTP11
2591 && m_CurrentRequest.HttpWriteMode==HttpWriteMode.Chunked
2592 && m_ResponseData.m_ResponseHeaders.Via != null
2593 && string.Compare(m_ResponseData.m_StatusDescription, "Bad Request ( The HTTP request includes a non-supported header. Contact the Server administrator. )", StringComparison.OrdinalIgnoreCase)==0) {
2594 GlobalLog.Print("Connection#" + ValidationHelper.HashString(this) + "::ParseResponseData() downgrading server to HTTP11PartiallyCompliant.");
2595 ServicePoint.HttpBehaviour = HttpBehaviour.HTTP11PartiallyCompliant;
2599 // If we have an HTTP continue, eat these headers and look
2602 // we got a 100 Continue. set this on the HttpWebRequest
2604 m_CurrentRequest.Saw100Continue = true;
2605 if (!ServicePoint.Understands100Continue) {
2607 // and start expecting it again if this behaviour was turned off
2609 GlobalLog.Print("Connection#" + ValidationHelper.HashString(this) + "::ParseResponseData() HttpWebRequest#" + ValidationHelper.HashString(m_CurrentRequest) + " ServicePoint#" + ValidationHelper.HashString(ServicePoint) + " sent UNexpected 100 Continue");
2610 ServicePoint.Understands100Continue = true;
2614 // set Continue Ack on request.
2616 GlobalLog.Print("Connection#" + ValidationHelper.HashString(this) + "::ParseResponseData() calling SetRequestContinue()");
2617 continueResponseData = m_ResponseData;
2619 //if we got a 100continue we ---- it and start looking for a final response
2620 goto case ReadState.Start;
2624 m_ReadState = ReadState.Data;
2625 goto case ReadState.Data;
2631 case ReadState.Data:
2633 // (check status code for continue handling)
2634 // 1. Figure out if its Chunked, content-length, or encoded
2635 // 2. Takes extra data, place in stream(s)
2636 // 3. Wake up blocked stream requests
2639 // Got through one entire response
2642 // parse and create a stream if needed
2643 parseStatus = ParseStreamData(ref returnResult);
2645 GlobalLog.Print("Connection#" + ValidationHelper.HashString(this) + "::ParseResponseData() result:" + parseStatus);
2646 GlobalLog.Print("Connection#" + ValidationHelper.HashString(this) + "::ParseResponseData()" + " WriteDone:" + m_WriteDone + " ReadDone:" + m_ReadDone + " WaitList:" + m_WaitList.Count + " WriteList:" + m_WriteList.Count);
2650 if (m_BytesScanned == m_BytesRead)
2653 GlobalLog.Print("Connection#" + ValidationHelper.HashString(this) + "::ParseResponseData() m_ReadState:" + m_ReadState);
2654 GlobalLog.Leave("Connection#" + ValidationHelper.HashString(this) + "::ParseResponseData()", parseStatus.ToString());
2660 /// Cause the Connection to Close and Abort its socket,
2661 /// after the next request is completed. If the Connection
2662 /// is already idle, then Aborts the socket immediately.
2665 internal void CloseOnIdle() {
2666 // The timer thread is allowed to call this. (It doesn't call user code and doesn't block.)
2667 GlobalLog.ThreadContract(ThreadKinds.Unknown, ThreadKinds.SafeSources | ThreadKinds.Timer, "Connection#" + ValidationHelper.HashString(this) + "::CloseOnIdle");
2670 m_KeepAlive = false;
2671 m_RemovedFromConnectionList = true;
2679 GC.SuppressFinalize(this);
2684 internal bool AbortOrDisassociate(HttpWebRequest request, WebException webException)
2686 GlobalLog.Enter("Connection#" + ValidationHelper.HashString(this) + "::AbortOrDisassociate", "request#" + ValidationHelper.HashString(request));
2687 GlobalLog.ThreadContract(ThreadKinds.Unknown, "Connection#" + ValidationHelper.HashString(this) + "::AbortOrDisassociate()");
2689 ConnectionReturnResult result = null;
2692 int idx = m_WriteList.IndexOf(request);
2693 // If the request is in the submission AND this is the first request we have to abort the connection,
2694 // Otheriwse we simply disassociate it from the current connection.
2697 WaitListItem foundItem = null;
2699 if (m_WaitList.Count > 0)
2701 foundItem = m_WaitList.Find(o => object.ReferenceEquals(o.Request, request));
2704 // If not found then the request must be already dispatched and the response stream is drained
2705 // If so then we let request.Abort() to deal with this situation.
2706 if (foundItem != null)
2708 NetworkingPerfCounters.Instance.IncrementAverage(NetworkingPerfCounterName.HttpWebRequestAvgQueueTime,
2709 foundItem.QueueStartTime);
2710 m_WaitList.Remove(foundItem);
2711 UnlockIfNeeded(foundItem.Request);
2714 GlobalLog.Leave("Connection#" + ValidationHelper.HashString(this) + "::AbortOrDisassociate()", "Request was wisassociated");
2719 // Make this connection Keep-Alive=false, remove the request and do not close the connection
2720 // When the active request completes, the rest of the pipeline (minus aborted request) will be resubmitted.
2721 m_WriteList.RemoveAt(idx);
2722 m_KeepAlive = false;
2723 GlobalLog.Leave("Connection#" + ValidationHelper.HashString(this) + "::AbortOrDisassociate()", "Request was Disassociated from the Write List, idx = " + idx);
2731 m_KeepAlive = false;
2732 if (webException != null && m_InnerException == null)
2734 m_InnerException = webException;
2735 m_Error = webException.Status;
2739 m_Error = WebExceptionStatus.RequestCanceled;
2742 PrepareCloseConnectionSocket(ref result);
2743 // Hard Close the socket.
2745 FreeReadBuffer(); // Do it after close completes to insure buffer not in use
2748 catch (Exception exception)
2750 t_LastStressException = exception;
2751 if (!NclUtilities.IsFatal(exception)){
2752 GlobalLog.Assert("Connection#" + ValidationHelper.HashString(this) + "::AbortOrDisassociate()", exception.Message);
2757 ConnectionReturnResult.SetResponses(result);
2758 GlobalLog.Leave("Connection#" + ValidationHelper.HashString(this) + "::AbortOrDisassociate()", "Connection Aborted");
2764 private static Exception t_LastStressException;
2767 internal void AbortSocket(bool isAbortState)
2769 // The timer/finalization thread is allowed to call this. (It doesn't call user code and doesn't block.)
2770 GlobalLog.ThreadContract(ThreadKinds.Unknown, ThreadKinds.SafeSources | ThreadKinds.Timer | ThreadKinds.Finalization, "Connection#" + ValidationHelper.HashString(this) + "::AbortSocket");
2771 GlobalLog.Enter("Connection#" + ValidationHelper.HashString(this) + "::Abort", "isAbortState:" + isAbortState.ToString());
2778 // This one is recoverable, set it to keep Read/Write StartNextRequest happy.
2779 m_Error = WebExceptionStatus.KeepAliveFailure;
2782 // Stream close isn't threadsafe.
2786 FreeReadBuffer(); // Do it after close completes to insure buffer not in use
2789 GlobalLog.Leave("Connection#" + ValidationHelper.HashString(this) + "::Abort", "isAbortState:" + isAbortState.ToString());
2795 PrepareCloseConnectionSocket - reset the connection requests list.
2797 This method is called when we want to close the conection.
2798 It must be called with the critical section held.
2799 The caller must call this.Close if decided to call this method.
2801 All connection closes (either ours or server initiated) eventually go through here.
2803 As to what we do: we loop through our write and wait list and pull requests
2804 off it, and give each request an error failure. Then the caller will
2805 dispatch the responses.
2809 private void PrepareCloseConnectionSocket(ref ConnectionReturnResult returnResult)
2811 GlobalLog.Enter("Connection#" + ValidationHelper.HashString(this) + "::PrepareCloseConnectionSocket", m_Error.ToString());
2813 // Effectivelly, closing a connection makes it exempted from the "Idling" logic
2814 m_IdleSinceUtc = DateTime.MinValue;
2815 CanBePooled = false;
2817 if (m_WriteList.Count != 0 || m_WaitList.Count != 0)
2820 GlobalLog.Print("Connection#" + ValidationHelper.HashString(this) + "::PrepareCloseConnectionSocket() m_WriteList.Count:" + m_WriteList.Count);
2821 DebugDumpWriteListEntries();
2822 GlobalLog.Print("Connection#" + ValidationHelper.HashString(this) + "::PrepareCloseConnectionSocket() m_WaitList.Count:" + m_WaitList.Count);
2823 DebugDumpWaitListEntries();
2825 HttpWebRequest lockedRequest = LockedRequest;
2827 if (lockedRequest != null)
2829 bool callUnlockRequest = false;
2830 GlobalLog.Print("Connection#" + ValidationHelper.HashString(this) + "::PrepareCloseConnectionSocket() looking for HttpWebRequest#" + ValidationHelper.HashString(lockedRequest));
2832 foreach (HttpWebRequest request in m_WriteList)
2834 if (request == lockedRequest) {
2835 callUnlockRequest = true;
2839 if (!callUnlockRequest) {
2840 foreach (WaitListItem item in m_WaitList) {
2841 if (item.Request == lockedRequest) {
2842 callUnlockRequest = true;
2847 if (callUnlockRequest) {
2852 HttpWebRequest[] requestArray = null;
2854 // WaitList gets Isolated exception status, free to retry multiple times
2855 if (m_WaitList.Count != 0)
2857 requestArray = new HttpWebRequest[m_WaitList.Count];
2858 for (int i = 0; i < m_WaitList.Count; i++)
2860 requestArray[i] = m_WaitList[i].Request;
2862 ConnectionReturnResult.AddExceptionRange(ref returnResult, requestArray, ExceptionHelper.IsolatedException);
2866 // WriteList (except for single request list) gets Recoverable exception status, may be retired if not failed once
2867 // For a single request list the exception is computed here
2868 // InnerExeption if any may tell more details in both cases
2870 if (m_WriteList.Count != 0)
2872 Exception theException = m_InnerException;
2875 if(theException != null)
2876 GlobalLog.Print(theException.ToString());
2878 GlobalLog.Print("m_Error = "+ m_Error.ToString());
2880 if (!(theException is WebException) && !(theException is SecurityException))
2882 if (m_Error == WebExceptionStatus.ServerProtocolViolation)
2884 string errorString = NetRes.GetWebStatusString(m_Error);
2886 string detailedInfo = "";
2887 if (m_ParseError.Section != WebParseErrorSection.Generic)
2888 detailedInfo += " Section=" + m_ParseError.Section.ToString();
2889 if (m_ParseError.Code != WebParseErrorCode.Generic) {
2890 detailedInfo += " Detail=" + SR.GetString("net_WebResponseParseError_" + m_ParseError.Code.ToString());
2892 if (detailedInfo.Length != 0)
2893 errorString += "." + detailedInfo;
2895 theException = new WebException(errorString,
2899 WebExceptionInternalStatus.RequestFatal);
2901 else if (m_Error == WebExceptionStatus.SecureChannelFailure)
2903 theException = new WebException(NetRes.GetWebStatusString("net_requestaborted", WebExceptionStatus.SecureChannelFailure),
2904 WebExceptionStatus.SecureChannelFailure);
2907 else if (m_Error == WebExceptionStatus.Timeout)
2909 theException = new WebException(NetRes.GetWebStatusString("net_requestaborted", WebExceptionStatus.Timeout),
2910 WebExceptionStatus.Timeout);
2912 else if(m_Error == WebExceptionStatus.RequestCanceled)
2914 theException = new WebException(NetRes.GetWebStatusString("net_requestaborted", WebExceptionStatus.RequestCanceled),
2915 WebExceptionStatus.RequestCanceled,
2916 WebExceptionInternalStatus.RequestFatal,
2919 else if(m_Error == WebExceptionStatus.MessageLengthLimitExceeded ||
2920 m_Error == WebExceptionStatus.TrustFailure)
2922 theException = new WebException(NetRes.GetWebStatusString("net_connclosed", m_Error),
2924 WebExceptionInternalStatus.RequestFatal,
2929 if (m_Error == WebExceptionStatus.Success)
2931 throw new InternalException(); // TODO: replace it with a generic error for the product bits
2932 //m_Error = WebExceptionStatus.UnknownError;
2936 bool isolatedKeepAliveFailure = false;
2938 if (m_WriteList.Count != 1)
2940 // Real scenario: SSL against IIS-5 would fail if pipelinning.
2941 // retry = true will cover a general case when >>the server<< aborts a pipeline
2942 // Basically all pipelined requests are marked with recoverable error including the very active request.
2945 else if (m_Error == WebExceptionStatus.KeepAliveFailure)
2947 HttpWebRequest request = (HttpWebRequest) m_WriteList[0];
2948 // Check that the active request did not start the body yet
2949 if (!request.BodyStarted)
2950 isolatedKeepAliveFailure = true;
2953 retry = (!AtLeastOneResponseReceived && !((HttpWebRequest) m_WriteList[0]).BodyStarted);
2955 theException = new WebException(NetRes.GetWebStatusString("net_connclosed", m_Error),
2957 (isolatedKeepAliveFailure? WebExceptionInternalStatus.Isolated:
2958 retry? WebExceptionInternalStatus.Recoverable:
2959 WebExceptionInternalStatus.RequestFatal),
2964 WebException pipelineException = new WebException(NetRes.GetWebStatusString("net_connclosed", WebExceptionStatus.PipelineFailure),
2965 WebExceptionStatus.PipelineFailure,
2966 WebExceptionInternalStatus.Recoverable,
2969 requestArray = new HttpWebRequest[m_WriteList.Count];
2970 m_WriteList.CopyTo(requestArray, 0);
2971 ConnectionReturnResult.AddExceptionRange(ref returnResult, requestArray, pipelineException, theException);
2975 foreach (WaitListItem item in m_WaitList) {
2976 GlobalLog.Print("Request removed from WaitList#"+ValidationHelper.HashString(item.Request));
2979 foreach (HttpWebRequest request in m_WriteList) {
2980 GlobalLog.Print("Request removed from m_WriteList#"+ValidationHelper.HashString(request));
2984 m_WriteList.Clear();
2986 foreach (WaitListItem item in m_WaitList)
2988 NetworkingPerfCounters.Instance.IncrementAverage(NetworkingPerfCounterName.HttpWebRequestAvgQueueTime,
2989 item.QueueStartTime);
2998 GC.SuppressFinalize(this);
3000 if (!m_RemovedFromConnectionList && ConnectionGroup != null)
3002 m_RemovedFromConnectionList = true;
3003 ConnectionGroup.Disassociate(this);
3006 GlobalLog.Leave("Connection#" + ValidationHelper.HashString(this) + "::PrepareCloseConnectionSocket");
3012 HandleError - Handle a protocol error from the server.
3014 This method is called when we've detected some sort of fatal protocol
3015 violation while parsing a response, receiving data from the server,
3016 or failing to connect to the server. We'll fabricate
3017 a WebException and then call CloseConnection which closes the
3018 connection as well as informs the request through a callback.
3021 webExceptionStatus -
3028 internal void HandleConnectStreamException(bool writeDone, bool readDone, WebExceptionStatus webExceptionStatus, ref ConnectionReturnResult returnResult, Exception e)
3030 if (m_InnerException == null)
3032 m_InnerException = e;
3033 if (!(e is WebException) && NetworkStream is TlsStream)
3035 // Unless a WebException is passed the Connection knows better the error code if the transport is TlsStream
3036 webExceptionStatus = ((TlsStream) NetworkStream).ExceptionStatus;
3038 else if (e is ObjectDisposedException)
3040 webExceptionStatus = WebExceptionStatus.RequestCanceled;
3043 HandleError(writeDone, readDone, webExceptionStatus, ref returnResult);
3046 private void HandleErrorWithReadDone(WebExceptionStatus webExceptionStatus, ref ConnectionReturnResult returnResult)
3048 HandleError(false, true, webExceptionStatus, ref returnResult);
3051 private void HandleError(bool writeDone, bool readDone, WebExceptionStatus webExceptionStatus, ref ConnectionReturnResult returnResult)
3060 GlobalLog.Print("Connection#" + ValidationHelper.HashString(this) + "::HandleError() m_WriteList.Count:" + m_WriteList.Count +
3061 " m_WaitList.Count:" + m_WaitList.Count +
3062 " new WriteDone:" + m_WriteDone + " new ReadDone:" + m_ReadDone);
3063 GlobalLog.Print("Connection#" + ValidationHelper.HashString(this) + "::HandleError() current HttpWebRequest#" + ValidationHelper.HashString(m_CurrentRequest));
3065 if(webExceptionStatus == WebExceptionStatus.Success)
3066 throw new InternalException(); //consider making an assert later.
3068 m_Error = webExceptionStatus;
3070 PrepareCloseConnectionSocket(ref returnResult);
3071 // This will kill the socket
3072 // Must be done inside the lock. (Stream Close() isn't threadsafe.)
3074 FreeReadBuffer(); // Do it after close completes to insure buffer not in use
3078 private static void ReadCallbackWrapper(IAsyncResult asyncResult)
3080 if (asyncResult.CompletedSynchronously)
3085 ((Connection) asyncResult.AsyncState).ReadCallback(asyncResult);
3089 /// <para>Performs read callback processing on connection
3090 /// handles getting headers parsed and streams created</para>
3092 private void ReadCallback(IAsyncResult asyncResult)
3094 GlobalLog.Enter("Connection#" + ValidationHelper.HashString(this) + "::ReadCallback", ValidationHelper.HashString(asyncResult));
3097 WebExceptionStatus errorStatus = WebExceptionStatus.ReceiveFailure;
3100 // parameter validation
3102 GlobalLog.Assert(asyncResult != null, "Connection#{0}::ReadCallback()|asyncResult == null", ValidationHelper.HashString(this));
3103 GlobalLog.Assert((asyncResult is OverlappedAsyncResult || asyncResult is LazyAsyncResult), "Connection#{0}::ReadCallback()|asyncResult is not OverlappedAsyncResult.", ValidationHelper.HashString(this));
3106 bytesRead = EndRead(asyncResult);
3108 bytesRead = -1; // 0 is reserved for re-entry on already buffered data
3110 errorStatus = WebExceptionStatus.Success;
3112 catch (Exception exception) {
3113 // Notify request's SubmitWriteStream that a socket error happened. This will cause future writes to
3114 // throw an IOException.
3115 HttpWebRequest curRequest = m_CurrentRequest;
3116 if (curRequest != null)
3118 curRequest.ErrorStatusCodeNotify(this, false, true);
3122 if (m_InnerException == null)
3123 m_InnerException = exception;
3125 if (exception.GetType() == typeof(ObjectDisposedException))
3126 errorStatus = WebExceptionStatus.RequestCanceled;
3130 // Consider: In case of a async exception we should do minimal cleanup here trying the appDomain
3131 // to survive or to force unloading of the appDomain .
3132 // need to handle SSL errors too
3133 if (NetworkStream is TlsStream) {
3134 errorStatus = ((TlsStream) NetworkStream).ExceptionStatus;
3137 errorStatus = WebExceptionStatus.ReceiveFailure;
3140 errorStatus = WebExceptionStatus.ReceiveFailure;
3141 #endif // !FEATURE_PAL
3142 GlobalLog.Print("Connection#" + ValidationHelper.HashString(this) + "::ReadCallback() EndRead() errorStatus:" + errorStatus.ToString() + " caught exception:" + exception);
3145 ReadComplete(bytesRead, errorStatus);
3146 GlobalLog.Leave("Connection#" + ValidationHelper.HashString(this) + "::ReadCallback");
3151 /// <para>Attempts to poll the socket, to see if data is waiting to be read,
3152 /// if there is data there, then a read is started</para>
3154 internal void PollAndRead(HttpWebRequest request, bool userRetrievedStream) {
3155 GlobalLog.ThreadContract(ThreadKinds.Unknown, "Connection#" + ValidationHelper.HashString(this) + "::PollAndRead");
3157 // Ensure that we don't already have a response for this request, before we attempt to read the socket.
3158 request.NeedsToReadForResponse = true;
3159 GlobalLog.Print("Connection#" + ValidationHelper.HashString(this) + "::PollAndRead() InternalPeekCompleted:" + request.ConnectionReaderAsyncResult.InternalPeekCompleted.ToString() + " Result:" + ValidationHelper.ToString(request.ConnectionReaderAsyncResult.Result));
3160 if (request.ConnectionReaderAsyncResult.InternalPeekCompleted && request.ConnectionReaderAsyncResult.Result == null && CanBePooled)
3162 SyncRead(request, userRetrievedStream, true);
3166 // Peforms a [....] Read and calls the ReadComplete to process the result
3167 // The reads are done iteratively, until the Request has received enough
3168 // data to contruct a response, or a 100-Continue is read, allowing the HttpWebRequest
3169 // to return a write stream
3171 // probeRead = true only for POST request and when the caller needs to wait for 100-continue
3173 internal void SyncRead(HttpWebRequest request, bool userRetrievedStream, bool probeRead)
3175 GlobalLog.Enter("Connection#" + ValidationHelper.HashString(this) + "::SyncRead(byte[]) request#" + ValidationHelper.HashString(request) + (probeRead? ", Probe read = TRUE":string.Empty));
3176 GlobalLog.ThreadContract(ThreadKinds.Sync, "Connection#" + ValidationHelper.HashString(this) + "::SyncRead");
3178 // prevent recursive calls to this function
3179 if (t_SyncReadNesting > 0) {
3180 GlobalLog.Leave("Connection#" + ValidationHelper.HashString(this) + "::SyncRead() - nesting");
3184 bool pollSuccess = probeRead? false: true;
3187 t_SyncReadNesting++;
3189 // grab a counter to tell us whenever the SetRequestContinue is called
3190 int requestContinueCount = probeRead ? request.RequestContinueCount : 0;
3194 WebExceptionStatus errorStatus = WebExceptionStatus.ReceiveFailure;
3197 if (m_BytesScanned < m_BytesRead)
3199 // left over from previous read
3201 bytesRead = 0; //tell it we want to use buffered data on the first iteration
3202 errorStatus = WebExceptionStatus.Success;
3211 errorStatus = WebExceptionStatus.ReceiveFailure;
3215 pollSuccess = Poll(request.ContinueTimeout * 1000, SelectMode.SelectRead); // Timeout is in microseconds
3216 GlobalLog.Print("Connection#" + ValidationHelper.HashString(this) + "::SyncRead() PollSuccess : " + pollSuccess);
3221 //Ensures that we'll timeout eventually on an appdomain unload.
3222 //Will be a no-op if the timeout doesn't change from request to request.
3223 ReadTimeout = request.Timeout;
3225 bytesRead = Read(m_ReadBuffer, m_BytesRead, m_ReadBuffer.Length - m_BytesRead);
3226 errorStatus = WebExceptionStatus.Success;
3228 bytesRead = -1; // 0 is reserved for re-entry on already buffered data
3232 catch (Exception exception)
3234 if (NclUtilities.IsFatal(exception)) throw;
3236 if (m_InnerException == null)
3237 m_InnerException = exception;
3239 if (exception.GetType() == typeof(ObjectDisposedException))
3240 errorStatus = WebExceptionStatus.RequestCanceled;
3242 // need to handle SSL errors too
3244 else if (NetworkStream is TlsStream) {
3245 errorStatus = ((TlsStream)NetworkStream).ExceptionStatus;
3247 #endif // !FEATURE_PAL
3250 SocketException socketException = exception.InnerException as SocketException;
3251 if (socketException != null)
3253 if (socketException.ErrorCode == (int) SocketError.TimedOut)
3254 errorStatus = WebExceptionStatus.Timeout;
3256 errorStatus = WebExceptionStatus.ReceiveFailure;
3260 GlobalLog.Print("Connection#" + ValidationHelper.HashString(this) + "::SyncRead() Read() threw errorStatus:" + errorStatus.ToString() + " bytesRead:" + bytesRead.ToString());
3264 requestDone = ReadComplete(bytesRead, errorStatus);
3267 } while (!requestDone && (userRetrievedStream || requestContinueCount == request.RequestContinueCount));
3270 t_SyncReadNesting--;
3275 // [....] 100-Continue wait only
3276 request.FinishContinueWait();
3279 if (!request.Saw100Continue && !userRetrievedStream)
3281 //During polling, we got a response that wasn't a 100 continue.
3282 request.NeedsToReadForResponse = false;
3287 GlobalLog.Print("Connection#" + ValidationHelper.HashString(this) + "::SyncRead() Poll has timed out, calling SetRequestContinue().");
3288 request.SetRequestContinue();
3291 GlobalLog.Leave("Connection#" + ValidationHelper.HashString(this) + "::SyncRead()");
3296 // Performs read callback processing on connection
3297 // handles getting headers parsed and streams created
3299 // bytesRead == 0 when we re-enter on buffered data without doing actual read
3300 // bytesRead == -1 when we got a connection close plus when errorStatus == sucess we got a g----ful close.
3301 // Otheriwse bytesRead is read byted to add to m_BytesRead i.e. to previously buffered data
3303 private bool ReadComplete(int bytesRead, WebExceptionStatus errorStatus)
3305 bool requestDone = true;
3306 CoreResponseData continueResponseData = null;
3307 ConnectionReturnResult returnResult = null;
3308 HttpWebRequest currentRequest = null;
3312 GlobalLog.Print("Connection#" + ValidationHelper.HashString(this) + "::ReadComplete() m_BytesRead:" + m_BytesRead.ToString() + " m_BytesScanned:" + m_BytesScanned + " (+= new bytesRead:" + bytesRead.ToString() + ")");
3316 // Means we might have gotten g----full or hard connection close.
3318 // If this is the first thing we read for a request then it
3319 // could be an idle connection closed by the server (isolated error)
3320 if (m_ReadState == ReadState.Start && m_AtLeastOneResponseReceived)
3322 // Note that KeepAliveFailure will be checked against POST-type requests
3323 // and it's fatal if the body was already started.
3324 if (errorStatus == WebExceptionStatus.Success || errorStatus == WebExceptionStatus.ReceiveFailure)
3325 errorStatus = WebExceptionStatus.KeepAliveFailure;
3327 else if (errorStatus == WebExceptionStatus.Success)
3329 // we got unexpected FIN in the middle of the response, or on a fresh connection, that's fatal
3330 errorStatus = WebExceptionStatus.ConnectionClosed;
3333 // Notify request's SubmitWriteStream that a socket error happened. This will cause future writes to
3334 // throw an IOException.
3335 HttpWebRequest curRequest = m_CurrentRequest;
3336 if (curRequest != null)
3338 curRequest.ErrorStatusCodeNotify(this, false, true);
3341 HandleErrorWithReadDone(errorStatus, ref returnResult);
3345 // Otherwise, we've got data.
3346 GlobalLog.Dump(m_ReadBuffer, m_BytesScanned, m_BytesRead - m_BytesScanned);
3347 GlobalLog.Dump(m_ReadBuffer, m_BytesRead, bytesRead);
3350 bytesRead += m_BytesRead;
3351 if (bytesRead > m_ReadBuffer.Length)
3352 throw new InternalException(); //in case we posted two receives at once
3353 m_BytesRead = bytesRead;
3355 // We have the parsing code seperated out in ParseResponseData
3357 // If we don't have all the headers yet. Resubmit the receive,
3358 // passing in the bytes read total as our index. When we get
3359 // back here we'll end up reparsing from the beginning, which is
3360 // OK. because this shouldn't be a performance case.
3362 //if we're back here, we need to reset the scanned bytes to 0.
3364 DataParseStatus parseStatus = ParseResponseData(ref returnResult, out requestDone, out continueResponseData);
3366 // copy off m_CurrentRequest as we might start processing a next request before exiting this method
3367 currentRequest = m_CurrentRequest;
3369 if (parseStatus != DataParseStatus.NeedMoreData)
3370 m_CurrentRequest = null;
3372 if (parseStatus == DataParseStatus.Invalid || parseStatus == DataParseStatus.DataTooBig)
3374 // Tell the request's SubmitWriteStream that the connection will be closed. It should ---- any
3375 // future writes so that the appropriate exception will be received in GetResponse().
3376 if (currentRequest != null)
3378 currentRequest.ErrorStatusCodeNotify(this, false, false);
3384 if (parseStatus == DataParseStatus.Invalid) {
3385 HandleErrorWithReadDone(WebExceptionStatus.ServerProtocolViolation, ref returnResult);
3388 HandleErrorWithReadDone(WebExceptionStatus.MessageLengthLimitExceeded, ref returnResult);
3390 GlobalLog.Print("Connection#" + ValidationHelper.HashString(this) + "::ReadComplete() parseStatus:" + parseStatus + " returnResult:" + returnResult);
3394 //Done means the ConnectStream take care of this connection until ConnectStream.CallDone()
3395 else if (parseStatus == DataParseStatus.Done)
3397 GlobalLog.Print("Connection#" + ValidationHelper.HashString(this) + "::ReadComplete() [The response stream is ready] parseStatus = DataParseStatus.Done");
3402 // we may reach the end of our buffer only when parsing headers.
3403 // this can happen when the header section is bigger than our initial 4k guess
3404 // which should be a good assumption in 99.9% of the cases. what we do here is:
3405 // 1) if there's a single BIG header (bigger than the current size) we will need to
3406 // grow the buffer before we move data over and read more data.
3407 // 2) move unparsed data to the beginning of the buffer and read more data in the
3408 // remaining part of the data.
3410 if (parseStatus == DataParseStatus.NeedMoreData)
3412 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());
3413 int unparsedDataSize = m_BytesRead - m_BytesScanned;
3414 if (unparsedDataSize != 0)
3416 if (m_BytesScanned == 0 && m_BytesRead == m_ReadBuffer.Length)
3419 // 1) we need to grow the buffer, move the unparsed data to the beginning of the buffer before reading more data.
3420 // since the buffer size is 4k, should we just double
3422 byte[] newReadBuffer = new byte[m_ReadBuffer.Length * 2 /*+ ReadBufferSize*/];
3423 Buffer.BlockCopy(m_ReadBuffer, 0, newReadBuffer, 0, m_BytesRead);
3425 // if m_ReadBuffer is from the pinnable cache, give it back
3427 m_ReadBuffer = newReadBuffer;
3432 // just move data around in the same buffer.
3434 Buffer.BlockCopy(m_ReadBuffer, m_BytesScanned, m_ReadBuffer, 0, unparsedDataSize);
3438 // update indexes and offsets in the new buffer
3440 m_BytesRead = unparsedDataSize;
3442 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());
3444 if (currentRequest != null)
3447 // This case means that we still parsing the headers, so need to post another read in the async case
3449 if (currentRequest.Async)
3451 GlobalLog.Print("Connection#" + ValidationHelper.HashString(this) + "::ReadComplete() Reposting Async Read. Buffer:" + ValidationHelper.HashString(m_ReadBuffer) + " BytesScanned:" + m_BytesScanned.ToString());
3453 if (Thread.CurrentThread.IsThreadPoolThread)
3455 GlobalLog.Print("Connection#" + ValidationHelper.HashString(this) + "::ReadComplete() Calling PostReceive().");
3460 // Offload to the threadpool to protect against the case where one request's thread posts IO that another request
3461 // depends on, but the first thread dies in the mean time.
3462 GlobalLog.Print("Connection#" + ValidationHelper.HashString(this) + "::ReadComplete() ThreadPool.UnsafeQueueUserWorkItem(m_PostReceiveDelegate, this)");
3463 ThreadPool.UnsafeQueueUserWorkItem(m_PostReceiveDelegate, this);
3470 // Any exception is processed by HandleError() and ----ed to avoid throwing on a thread pool
3471 // In the [....] case the HandleError() will abort the request so the caller will pick up the result.
3473 catch (Exception exception) {
3474 if (NclUtilities.IsFatal(exception)) throw;
3478 if (m_InnerException == null)
3479 m_InnerException = exception;
3481 // Notify request's SubmitWriteStream that a socket error happened. This will cause future writes to
3482 // throw an IOException.
3483 HttpWebRequest curRequest = m_CurrentRequest;
3484 if (curRequest != null)
3486 curRequest.ErrorStatusCodeNotify(this, false, true);
3489 HandleErrorWithReadDone(WebExceptionStatus.ReceiveFailure, ref returnResult);
3494 // It is only safe to continue if there was a 100 continue OR buffering is supported.
3495 if (currentRequest != null && currentRequest.HttpWriteMode != HttpWriteMode.None &&
3496 (continueResponseData != null
3497 // not a 100 continue, but we have buffering so we don't care what it was.
3498 || (returnResult != null && returnResult.IsNotEmpty && currentRequest.AllowWriteStreamBuffering)
3502 // if returnResult is not empty it must also contain some result for the currently active request
3503 // Since it could be a POST request waiting on the body submit, signal the body here
3504 if (currentRequest.FinishContinueWait())
3506 currentRequest.SetRequestContinue(continueResponseData);
3511 ConnectionReturnResult.SetResponses(returnResult);
3518 // This method is called by ConnectStream, only when resubmitting
3519 // We have sent the headers already in HttpWebRequest.EndSubmitRequest()
3520 // which calls ConnectStream.WriteHeaders() which calls to HttpWebRequest.EndWriteHeaders()
3521 // which calls ConnectStream.ResubmitWrite() which calls in here
3522 internal void Write(ScatterGatherBuffers writeBuffer) {
3523 GlobalLog.Enter("Connection#" + ValidationHelper.HashString(this) + "::Write(ScatterGatherBuffers) networkstream#" + ValidationHelper.HashString(NetworkStream));
3525 // parameter validation
3527 GlobalLog.Assert(writeBuffer != null, "Connection#{0}::Write(ScatterGatherBuffers)|writeBuffer == null", ValidationHelper.HashString(this));
3529 // set up array for MultipleWrite call
3530 // note that GetBuffers returns null if we never wrote to it.
3532 BufferOffsetSize[] buffers = writeBuffer.GetBuffers();
3533 if (buffers!=null) {
3535 // This will block writing the buffers out.
3537 MultipleWrite(buffers);
3539 GlobalLog.Leave("Connection#" + ValidationHelper.HashString(this) + "::Write(ScatterGatherBuffers) this:" + ValidationHelper.HashString(this) + " writeBuffer.Size:" + writeBuffer.Length.ToString());
3545 PostReceiveWrapper - Post a receive from a worker thread.
3547 This is our delegate, used for posting receives from a worker thread.
3548 We do this when we can't be sure that we're already on a worker thread,
3549 and we don't want to post from a client thread because if it goes away
3554 state - a null object
3559 private static void PostReceiveWrapper(object state) {
3560 Connection thisConnection = state as Connection;
3561 GlobalLog.Enter("Connection#" + ValidationHelper.HashString(thisConnection) + "::PostReceiveWrapper", "Cnt#" + ValidationHelper.HashString(thisConnection));
3562 GlobalLog.Assert(thisConnection != null, "Connection#{0}::PostReceiveWrapper()|thisConnection == null", ValidationHelper.HashString(thisConnection));
3564 thisConnection.PostReceive();
3566 GlobalLog.Leave("Connection#" + ValidationHelper.HashString(thisConnection) + "::PostReceiveWrapper");
3569 private void PostReceive()
3571 GlobalLog.Enter("Connection#" + ValidationHelper.HashString(this) + "::PostReceive", "");
3573 GlobalLog.Print("Connection#" + ValidationHelper.HashString(this) + "::PostReceive() m_ReadBuffer:" + ValidationHelper.HashString(m_ReadBuffer) + " Length:" + m_ReadBuffer.Length.ToString());
3577 GlobalLog.Assert(m_BytesScanned == 0, "PostReceive()|A receive should not be posted when m_BytesScanned != 0 (the data should be moved to offset 0).");
3579 if (m_LastAsyncResult != null && !m_LastAsyncResult.IsCompleted)
3580 throw new InternalException(); //This may cause duplicate requests if we let it through in retail
3583 m_LastAsyncResult = UnsafeBeginRead(m_ReadBuffer, m_BytesRead, m_ReadBuffer.Length - m_BytesRead, m_ReadCallback, this);
3584 if (m_LastAsyncResult.CompletedSynchronously)
3587 ReadCallback(m_LastAsyncResult);
3590 catch (Exception exception) {
3591 // Notify request's SubmitWriteStream that a socket error happened. This will cause future writes to
3592 // throw an IOException.
3593 HttpWebRequest curRequest = m_CurrentRequest;
3594 if (curRequest != null)
3596 curRequest.ErrorStatusCodeNotify(this, false, true);
3600 ConnectionReturnResult returnResult = null;
3601 HandleErrorWithReadDone(WebExceptionStatus.ReceiveFailure, ref returnResult);
3602 ConnectionReturnResult.SetResponses(returnResult);
3603 GlobalLog.LeaveException("Connection#" + ValidationHelper.HashString(this) + "::PostReceive", exception);
3606 GlobalLog.Leave("Connection#" + ValidationHelper.HashString(this) + "::PostReceive");
3611 private static void TunnelThroughProxyWrapper(IAsyncResult result){
3612 if(result.CompletedSynchronously){
3616 bool success = false;
3617 WebExceptionStatus ws = WebExceptionStatus.ConnectFailure;
3618 HttpWebRequest req = (HttpWebRequest)((LazyAsyncResult)result).AsyncObject;
3619 Connection conn = (Connection)((TunnelStateObject)result.AsyncState).Connection;
3620 HttpWebRequest originalReq = (HttpWebRequest)((TunnelStateObject)result.AsyncState).OriginalRequest;
3622 GlobalLog.Enter("Connection#" + ValidationHelper.HashString(conn) + "::TunnelThroughProxyCallback");
3625 req.EndGetResponse(result);
3626 HttpWebResponse connectResponse = (HttpWebResponse)req.GetResponse();
3627 ConnectStream connectStream = (ConnectStream)connectResponse.GetResponseStream();
3629 // this stream will be used as the real stream for TlsStream
3630 conn.NetworkStream = new NetworkStream(connectStream.Connection.NetworkStream, true);
3631 // This will orphan the original connect stream now owned by tunnelStream
3632 connectStream.Connection.NetworkStream.ConvertToNotSocketOwner();
3636 catch (Exception exception) {
3637 if (conn.m_InnerException == null)
3638 conn.m_InnerException = exception;
3640 if (exception is WebException) {
3641 ws = ((WebException)exception).Status;
3644 GlobalLog.Print("Connection#" + ValidationHelper.HashString(conn) + "::TunnelThroughProxyCallback() exception occurred: " + exception);
3648 ConnectionReturnResult returnResult = null;
3649 conn.HandleError(false, false, ws, ref returnResult);
3650 ConnectionReturnResult.SetResponses(returnResult);
3654 conn.CompleteConnection(true, originalReq);
3655 GlobalLog.Leave("Connection#" + ValidationHelper.HashString(conn) + "::TunnelThroughProxyCallback");
3662 private bool TunnelThroughProxy(Uri proxy, HttpWebRequest originalRequest, bool async) {
3663 GlobalLog.Enter("Connection#" + ValidationHelper.HashString(this) + "::TunnelThroughProxy", "proxy="+proxy+", async="+async+", originalRequest #"+ValidationHelper.HashString(originalRequest));
3665 bool result = false;
3666 HttpWebRequest connectRequest = null;
3667 HttpWebResponse connectResponse = null;
3670 (new WebPermission(NetworkAccess.Connect, proxy)).Assert();
3672 connectRequest = new HttpWebRequest(
3674 originalRequest.Address,
3675 // new Uri("https://" + originalRequest.Address.GetParts(UriComponents.HostAndPort, UriFormat.UriEscaped)),
3680 WebPermission.RevertAssert();
3683 connectRequest.Credentials = originalRequest.InternalProxy == null ? null : originalRequest.InternalProxy.Credentials;
3684 connectRequest.InternalProxy = null;
3685 connectRequest.PreAuthenticate = true;
3688 TunnelStateObject o = new TunnelStateObject(originalRequest, this);
3689 IAsyncResult asyncResult = connectRequest.BeginGetResponse(m_TunnelCallback, o);
3690 if(!asyncResult.CompletedSynchronously){
3691 GlobalLog.Leave("Connection#" + ValidationHelper.HashString(this) + "::TunnelThroughProxy completed asynchronously", true);
3694 connectResponse = (HttpWebResponse)connectRequest.EndGetResponse(asyncResult);
3697 connectResponse = (HttpWebResponse)connectRequest.GetResponse();
3700 ConnectStream connectStream = (ConnectStream)connectResponse.GetResponseStream();
3702 // this stream will be used as the real stream for TlsStream
3703 NetworkStream = new NetworkStream(connectStream.Connection.NetworkStream, true);
3704 // This will orphan the original connect stream now owned by tunnelStream
3705 connectStream.Connection.NetworkStream.ConvertToNotSocketOwner();
3708 catch (Exception exception) {
3709 if (m_InnerException == null)
3710 m_InnerException = exception;
3711 GlobalLog.Print("Connection#" + ValidationHelper.HashString(this) + "::TunnelThroughProxy() exception occurred: " + exception);
3714 GlobalLog.Leave("Connection#" + ValidationHelper.HashString(this) + "::TunnelThroughProxy", result);
3720 // CheckNonIdle - called after update of the WriteList/WaitList,
3721 // upon the departure of our Idle state our, BusyCount will
3722 // go to non-0, then we need to mark this transition
3725 private void CheckNonIdle() {
3726 GlobalLog.Print("Connection#" + ValidationHelper.HashString(this) + "::CheckNonIdle()");
3727 if (m_Idle && this.BusyCount != 0) {
3729 ServicePoint.IncrementConnection();
3730 ConnectionGroup.IncrementConnection();
3735 // CheckIdle - called after update of the WriteList/WaitList,
3736 // specifically called after we remove entries
3739 private void CheckIdle() {
3740 // The timer thread is allowed to call this. (It doesn't call user code and doesn't block.)
3741 GlobalLog.ThreadContract(ThreadKinds.Unknown, ThreadKinds.SafeSources | ThreadKinds.Finalization | ThreadKinds.Timer, "Connection#" + ValidationHelper.HashString(this) + "::CheckIdle");
3743 GlobalLog.Print("Connection#" + ValidationHelper.HashString(this) + "::CheckIdle() m_Idle = " + m_Idle + ", BusyCount = " + BusyCount);
3744 if (!m_Idle && this.BusyCount == 0) {
3746 ServicePoint.DecrementConnection();
3747 if (ConnectionGroup != null) {
3748 GlobalLog.Print("Connection#" + ValidationHelper.HashString(this) + "::CheckIdle() - calling ConnectionGoneIdle()");
3749 ConnectionGroup.DecrementConnection();
3750 ConnectionGroup.ConnectionGoneIdle();
3752 // Remember the moment when this connection went idle.
3753 m_IdleSinceUtc = DateTime.UtcNow;
3758 // DebugDumpArrayListEntries - debug goop
3760 [Conditional("TRAVE")]
3761 private void DebugDumpWriteListEntries() {
3762 for (int i = 0; i < m_WriteList.Count; i++)
3764 DebugDumpListEntry(i, m_WriteList[i] as HttpWebRequest, "WriteList");
3768 [Conditional("TRAVE")]
3769 private void DebugDumpWaitListEntries() {
3770 for (int i = 0; i < m_WaitList.Count; i++)
3772 DebugDumpListEntry(i, m_WaitList[i].Request, "WaitList");
3776 [Conditional("TRAVE")]
3777 private void DebugDumpListEntry(int currentPos, HttpWebRequest req, string listType) {
3778 GlobalLog.Print("WaitList[" + currentPos.ToString() + "] Req: #" +
3779 ValidationHelper.HashString(req));
3783 // Validation & debugging
3785 [System.Diagnostics.Conditional("DEBUG")]
3786 internal void DebugMembers(int requestHash) {
3788 bool dump = requestHash==0;
3789 GlobalLog.Print("Cnt#" + this.GetHashCode());
3791 foreach(HttpWebRequest request in m_WriteList) {
3792 if (request.GetHashCode() == requestHash) {
3793 GlobalLog.Print("Found-WriteList");
3798 foreach(WaitListItem item in m_WaitList) {
3799 if (item.Request.GetHashCode() == requestHash) {
3800 GlobalLog.Print("Found-WaitList");
3808 DebugDumpWriteListEntries();
3809 DebugDumpWaitListEntries();
3815 [System.Diagnostics.Conditional("TRAVE")]
3816 internal void Dump() {
3817 GlobalLog.Print("CanPipeline:" + m_CanPipeline);
3818 GlobalLog.Print("Pipelining:" + m_Pipelining);
3819 GlobalLog.Print("KeepAlive:" + m_KeepAlive);
3820 GlobalLog.Print("m_Error:" + m_Error);
3821 GlobalLog.Print("m_ReadBuffer:" + m_ReadBuffer);
3822 GlobalLog.Print("m_BytesRead:" + m_BytesRead);
3823 GlobalLog.Print("m_BytesScanned:" + m_BytesScanned);
3824 GlobalLog.Print("m_ResponseData:" + m_ResponseData);
3825 GlobalLog.Print("m_ReadState:" + m_ReadState);
3826 GlobalLog.Print("m_StatusState:" + m_StatusState);
3827 GlobalLog.Print("ConnectionGroup:" + ConnectionGroup);
3828 GlobalLog.Print("Idle:" + m_Idle);
3829 GlobalLog.Print("ServicePoint:" + ServicePoint);
3830 GlobalLog.Print("m_Version:" + ServicePoint.ProtocolVersion);
3831 GlobalLog.Print("NetworkStream:" + NetworkStream);
3833 if ( NetworkStream is TlsStream) {
3834 TlsStream tlsStream = NetworkStream as TlsStream;
3835 tlsStream.DebugMembers();
3838 #endif // !FEATURE_PAL
3839 if (NetworkStream != null) {
3840 NetworkStream.DebugMembers();
3842 GlobalLog.Print("m_AbortDelegate:" + m_AbortDelegate);
3843 GlobalLog.Print("ReadDone:" + m_ReadDone);
3844 GlobalLog.Print("WriteDone:" + m_WriteDone);
3845 GlobalLog.Print("Free:" + m_Free);