private bool ReadComplete(int bytesRead, WebExceptionStatus errorStatus)
{
bool requestDone = true;
CoreResponseData continueResponseData = null;
ConnectionReturnResult returnResult = null;
HttpWebRequest currentRequest = null;
try
{
GlobalLog.Print("Connection#" + ValidationHelper.HashString(this) + "::ReadComplete() m_BytesRead:" + m_BytesRead.ToString() + " m_BytesScanned:" + m_BytesScanned + " (+= new bytesRead:" + bytesRead.ToString() + ")");
if (bytesRead < 0)
{
// Means we might have gotten gracefull or hard connection close.
// If this is the first thing we read for a request then it
// could be an idle connection closed by the server (isolated error)
if (m_ReadState == ReadState.Start && m_AtLeastOneResponseReceived)
{
// Note that KeepAliveFailure will be checked against POST-type requests
// and it's fatal if the body was already started.
if (errorStatus == WebExceptionStatus.Success || errorStatus == WebExceptionStatus.ReceiveFailure)
errorStatus = WebExceptionStatus.KeepAliveFailure;
}
else if (errorStatus == WebExceptionStatus.Success)
{
// we got unexpected FIN in the middle of the response, or on a fresh connection, that's fatal
errorStatus = WebExceptionStatus.ConnectionClosed;
}
// Notify request's SubmitWriteStream that a socket error happened. This will cause future writes to
// throw an IOException.
HttpWebRequest curRequest = m_CurrentRequest;
if (curRequest != null)
{
curRequest.ErrorStatusCodeNotify(this, false, true);
}
HandleErrorWithReadDone(errorStatus, ref returnResult);
goto done;
}
// Otherwise, we've got data.
GlobalLog.Dump(m_ReadBuffer, m_BytesScanned, m_BytesRead - m_BytesScanned);
GlobalLog.Dump(m_ReadBuffer, m_BytesRead, bytesRead);
bytesRead += m_BytesRead;
if (bytesRead > m_ReadBuffer.Length)
throw new InternalException(); //in case we posted two receives at once
m_BytesRead = bytesRead;
// We have the parsing code seperated out in ParseResponseData
//
// If we don't have all the headers yet. Resubmit the receive,
// passing in the bytes read total as our index. When we get
// back here we'll end up reparsing from the beginning, which is
// OK. because this shouldn't be a performance case.
//if we're back here, we need to reset the scanned bytes to 0.
DataParseStatus parseStatus = ParseResponseData(ref returnResult, out requestDone, out continueResponseData);
// copy off m_CurrentRequest as we might start processing a next request before exiting this method
currentRequest = m_CurrentRequest;
if (parseStatus != DataParseStatus.NeedMoreData)
m_CurrentRequest = null;
if (parseStatus == DataParseStatus.Invalid || parseStatus == DataParseStatus.DataTooBig)
{
// Tell the request's SubmitWriteStream that the connection will be closed. It should swallow any
// future writes so that the appropriate exception will be received in GetResponse().
if (currentRequest != null)
{
currentRequest.ErrorStatusCodeNotify(this, false, false);
}
//
// report error
//
if (parseStatus == DataParseStatus.Invalid) {
HandleErrorWithReadDone(WebExceptionStatus.ServerProtocolViolation, ref returnResult);
}
else {
HandleErrorWithReadDone(WebExceptionStatus.MessageLengthLimitExceeded, ref returnResult);
}
GlobalLog.Print("Connection#" + ValidationHelper.HashString(this) + "::ReadComplete() parseStatus:" + parseStatus + " returnResult:" + returnResult);
goto done;
}
//Done means the ConnectStream take care of this connection until ConnectStream.CallDone()
else if (parseStatus == DataParseStatus.Done)
{
GlobalLog.Print("Connection#" + ValidationHelper.HashString(this) + "::ReadComplete() [The response stream is ready] parseStatus = DataParseStatus.Done");
goto done;
}
//
// we may reach the end of our buffer only when parsing headers.
// this can happen when the header section is bigger than our initial 4k guess
// which should be a good assumption in 99.9% of the cases. what we do here is:
// 1) if there's a single BIG header (bigger than the current size) we will need to
// grow the buffer before we move data over and read more data.
// 2) move unparsed data to the beginning of the buffer and read more data in the
// remaining part of the data.
//
if (parseStatus == DataParseStatus.NeedMoreData)
{
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());
int unparsedDataSize = m_BytesRead - m_BytesScanned;
if (unparsedDataSize != 0)
{
if (m_BytesScanned == 0 && m_BytesRead == m_ReadBuffer.Length)
{
//
// 1) we need to grow the buffer, move the unparsed data to the beginning of the buffer before reading more data.
// since the buffer size is 4k, should we just double
//
byte[] newReadBuffer = new byte[m_ReadBuffer.Length * 2 /*+ ReadBufferSize*/];
Buffer.BlockCopy(m_ReadBuffer, 0, newReadBuffer, 0, m_BytesRead);
m_ReadBuffer = newReadBuffer;
}
else
{
//
// just move data around in the same buffer.
//
Buffer.BlockCopy(m_ReadBuffer, m_BytesScanned, m_ReadBuffer, 0, unparsedDataSize);
}
}
//
// update indexes and offsets in the new buffer
//
m_BytesRead = unparsedDataSize;
m_BytesScanned = 0;
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());
if (currentRequest != null)
{
//
// This case means that we still parsing the headers, so need to post another read in the async case
if (currentRequest.Async)
{
GlobalLog.Print("Connection#" + ValidationHelper.HashString(this) + "::ReadComplete() Reposting Async Read. Buffer:" + ValidationHelper.HashString(m_ReadBuffer) + " BytesScanned:" + m_BytesScanned.ToString());
if (Thread.CurrentThread.IsThreadPoolThread)
{
GlobalLog.Print("Connection#" + ValidationHelper.HashString(this) + "::ReadComplete() Calling PostReceive().");
PostReceive();
}
else
{
// Offload to the threadpool to protect against the case where one request's thread posts IO that another request
// depends on, but the first thread dies in the mean time.
GlobalLog.Print("Connection#" + ValidationHelper.HashString(this) + "::ReadComplete() ThreadPool.UnsafeQueueUserWorkItem(m_PostReceiveDelegate, this)");
ThreadPool.UnsafeQueueUserWorkItem(m_PostReceiveDelegate, this);
}
}
}
}
}
//
// Any exception is processed by HandleError() and swallowed to avoid throwing on a thread pool
// In the sync case the HandleError() will abort the request so the caller will pick up the result.
//
catch (Exception exception) {
if (NclUtilities.IsFatal(exception)) throw;
requestDone = true;
if (m_InnerException == null)
m_InnerException = exception;
// Notify request's SubmitWriteStream that a socket error happened. This will cause future writes to
// throw an IOException.
HttpWebRequest curRequest = m_CurrentRequest;
if (curRequest != null)
{
curRequest.ErrorStatusCodeNotify(this, false, true);
}
HandleErrorWithReadDone(WebExceptionStatus.ReceiveFailure, ref returnResult);
}
done:
try {
if((continueResponseData != null || (returnResult != null && returnResult.IsNotEmpty)) && currentRequest != null)
{
// if returnResult is not empty it must also contain some result for the currently active request
// Since it could be a POST request waiting on the body submit, signal the body here
currentRequest.SetRequestContinue(continueResponseData);
}
}
finally {
ConnectionReturnResult.SetResponses(returnResult);
}
return requestDone;
}