mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-26 14:22:01 +00:00
implement HTTP/1.1 pipelining, b=93054
r=gagan, sr=rpotts
This commit is contained in:
parent
d1eef69879
commit
b4db8085e5
@ -115,11 +115,23 @@ interface nsIHttpChannel : nsIChannel
|
||||
|
||||
/**
|
||||
* This attribute controls whether or not content conversion should be
|
||||
* done per the Content-Encoding response header. Its value is true
|
||||
* by default.
|
||||
* done per the Content-Encoding response header.
|
||||
*
|
||||
* TRUE by default.
|
||||
*/
|
||||
attribute boolean applyConversion;
|
||||
|
||||
/**
|
||||
* This attribute is a hint to the channel to indicate whether or not
|
||||
* the underlying HTTP transaction should be allowed to be pipelined
|
||||
* with other transactions. This should be set to FALSE, for example,
|
||||
* if the application knows that the corresponding document is likely
|
||||
* to be very large.
|
||||
*
|
||||
* TRUE by default, though other factors may prevent pipelining.
|
||||
*/
|
||||
attribute boolean allowPipelining;
|
||||
|
||||
/**
|
||||
* This attribute specifies the number of redirects this channel is allowed
|
||||
* to make. If zero, the channel will fail to redirect and will generate
|
||||
|
@ -51,6 +51,7 @@ CPPSRCS = \
|
||||
nsHttpAuthCache.cpp \
|
||||
nsHttpBasicAuth.cpp \
|
||||
nsHttpDigestAuth.cpp \
|
||||
nsHttpPipeline.cpp \
|
||||
$(NULL)
|
||||
|
||||
LOCAL_INCLUDES=-I$(srcdir)/../../../streamconv/converters
|
||||
|
@ -43,6 +43,7 @@ CPP_OBJS= \
|
||||
.\$(OBJDIR)\nsHttpHeaderArray.obj \
|
||||
.\$(OBJDIR)\nsHttpConnection.obj \
|
||||
.\$(OBJDIR)\nsHttpTransaction.obj \
|
||||
.\$(OBJDIR)\nsHttpPipeline.obj \
|
||||
.\$(OBJDIR)\nsHttpChannel.obj \
|
||||
.\$(OBJDIR)\nsHttpRequestHead.obj \
|
||||
.\$(OBJDIR)\nsHttpResponseHead.obj \
|
||||
|
@ -44,7 +44,7 @@ public:
|
||||
|
||||
// called by a transaction when the transaction reads more from the socket
|
||||
// than it should have (eg. containing part of the next pipelined response).
|
||||
virtual nsresult PushBack(char *data, PRUint32 length) = 0;
|
||||
virtual nsresult PushBack(const char *data, PRUint32 length) = 0;
|
||||
};
|
||||
|
||||
#endif // nsAHttpConnection_h__
|
||||
|
@ -25,6 +25,10 @@ public:
|
||||
// socket transport.
|
||||
virtual void GetNotificationCallbacks(nsIInterfaceRequestor **) = 0;
|
||||
|
||||
// called by the pipelining code to determine how much memory to allocate
|
||||
// for this transaction's request headers.
|
||||
virtual PRUint32 GetRequestSize() = 0;
|
||||
|
||||
// called by the connection to indicate that the socket can be written to.
|
||||
// the transaction returns NS_BASE_STREAM_CLOSED when it is finished
|
||||
// writing its request(s).
|
||||
|
@ -78,6 +78,9 @@ typedef PRUint8 nsHttpVersion;
|
||||
#define NS_HTTP_ALLOW_KEEPALIVE (1<<0)
|
||||
#define NS_HTTP_ALLOW_PIPELINING (1<<1)
|
||||
|
||||
// hard upper limit on the number of requests that can be pipelined
|
||||
#define NS_HTTP_MAX_PIPELINED_REQUESTS 10
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// http atoms...
|
||||
//-----------------------------------------------------------------------------
|
||||
|
@ -69,6 +69,7 @@ nsHttpChannel::nsHttpChannel()
|
||||
, mRedirectionLimit(nsHttpHandler::get()->RedirectionLimit())
|
||||
, mIsPending(PR_FALSE)
|
||||
, mApplyConversion(PR_TRUE)
|
||||
, mAllowPipelining(PR_TRUE)
|
||||
, mFromCacheOnly(PR_FALSE)
|
||||
, mCachedContentIsValid(PR_FALSE)
|
||||
, mCachedContentIsPartial(PR_FALSE)
|
||||
@ -378,8 +379,20 @@ nsHttpChannel::SetupTransaction()
|
||||
NS_HTTP_BUFFER_SIZE);
|
||||
if (NS_FAILED(rv)) return rv;
|
||||
|
||||
// figure out if we should disallow pipelining. disallow if
|
||||
// the method is not GET or HEAD. we also disallow pipelining
|
||||
// if this channel corresponds to a toplevel document.
|
||||
// XXX does the toplevel document check really belong here?
|
||||
// XXX or, should we push it out entirely to necko consumers?
|
||||
PRUint8 caps = mCapabilities;
|
||||
if (!mAllowPipelining || (mURI == mDocumentURI) ||
|
||||
!(mRequestHead.Method() == nsHttp::Get || mRequestHead.Method() == nsHttp::Head)) {
|
||||
LOG(("nsHttpChannel::SetupTransaction [this=%x] pipelining disallowed\n", this));
|
||||
caps &= ~NS_HTTP_ALLOW_PIPELINING;
|
||||
}
|
||||
|
||||
// create the transaction object
|
||||
mTransaction = new nsHttpTransaction(listenerProxy, this, mCapabilities);
|
||||
mTransaction = new nsHttpTransaction(listenerProxy, this, caps);
|
||||
if (!mTransaction)
|
||||
return NS_ERROR_OUT_OF_MEMORY;
|
||||
NS_ADDREF(mTransaction);
|
||||
@ -1493,6 +1506,8 @@ nsHttpChannel::ProcessRedirection(PRUint32 redirectType)
|
||||
httpChannel->SetReferrer(mReferrer, mReferrerType);
|
||||
// convey the mApplyConversion flag (bug 91862)
|
||||
httpChannel->SetApplyConversion(mApplyConversion);
|
||||
// convey the mAllowPipelining flag
|
||||
httpChannel->SetAllowPipelining(mAllowPipelining);
|
||||
// convey the new redirection limit
|
||||
httpChannel->SetRedirectionLimit(mRedirectionLimit - 1);
|
||||
}
|
||||
@ -2700,6 +2715,23 @@ nsHttpChannel::SetApplyConversion(PRBool value)
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsHttpChannel::GetAllowPipelining(PRBool *value)
|
||||
{
|
||||
NS_ENSURE_ARG_POINTER(value);
|
||||
*value = mAllowPipelining;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsHttpChannel::SetAllowPipelining(PRBool value)
|
||||
{
|
||||
if (mIsPending)
|
||||
return NS_ERROR_FAILURE;
|
||||
mAllowPipelining = value;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsHttpChannel::GetRedirectionLimit(PRUint32 *value)
|
||||
{
|
||||
@ -2777,6 +2809,10 @@ nsHttpChannel::OnStopRequest(nsIRequest *request, nsISupports *ctxt, nsresult st
|
||||
LOG(("nsHttpChannel::OnStopRequest [this=%x request=%x status=%x]\n",
|
||||
this, request, status));
|
||||
|
||||
// honor the cancelation status even if the underlying transaction completed.
|
||||
if (mCanceled)
|
||||
status = mStatus;
|
||||
|
||||
// if the request is a previous transaction, then simply release it.
|
||||
if (request == mPrevTransaction) {
|
||||
NS_RELEASE(mPrevTransaction);
|
||||
@ -2810,8 +2846,19 @@ nsHttpChannel::OnStopRequest(nsIRequest *request, nsISupports *ctxt, nsresult st
|
||||
|
||||
PRBool isPartial = PR_FALSE;
|
||||
if (mTransaction) {
|
||||
// find out if the transaction ran to completion...
|
||||
isPartial = !mTransaction->ResponseIsComplete();
|
||||
if (mCacheEntry) {
|
||||
// find out if the transaction ran to completion...
|
||||
isPartial = !mTransaction->ResponseIsComplete();
|
||||
#if 0
|
||||
if (!isPartial) {
|
||||
// even if the transaction ran to completion, we might not
|
||||
// have written all of the data into the cache.
|
||||
PRUint32 entrySize = 0;
|
||||
if (NS_SUCCEEDED(mCacheEntry->GetDataSize(&entrySize)))
|
||||
isPartial = (entrySize < mTransaction->GetContentRead());
|
||||
}
|
||||
#endif
|
||||
}
|
||||
// at this point, we're done with the transaction
|
||||
NS_RELEASE(mTransaction);
|
||||
mTransaction = nsnull;
|
||||
@ -2869,6 +2916,10 @@ nsHttpChannel::OnDataAvailable(nsIRequest *request, nsISupports *ctxt,
|
||||
LOG(("nsHttpChannel::OnDataAvailable [this=%x request=%x offset=%u count=%u]\n",
|
||||
this, request, offset, count));
|
||||
|
||||
// don't send out OnDataAvailable notifications if we've been canceled.
|
||||
if (mCanceled)
|
||||
return mStatus;
|
||||
|
||||
if (mCachedContentIsPartial && (request == mTransaction)) {
|
||||
// XXX we can eliminate this buffer once bug 93055 is resolved.
|
||||
return BufferPartialContent(input, count);
|
||||
|
@ -185,6 +185,7 @@ private:
|
||||
|
||||
PRPackedBool mIsPending;
|
||||
PRPackedBool mApplyConversion;
|
||||
PRPackedBool mAllowPipelining;
|
||||
PRPackedBool mFromCacheOnly;
|
||||
PRPackedBool mCachedContentIsValid;
|
||||
PRPackedBool mCachedContentIsPartial;
|
||||
|
@ -55,6 +55,7 @@ nsHttpConnection::nsHttpConnection()
|
||||
, mReadStartTime(0)
|
||||
, mLastActiveTime(0)
|
||||
, mIdleTimeout(0)
|
||||
, mServerVersion(NS_HTTP_VERSION_1_0) // assume low-grade server
|
||||
, mKeepAlive(1) // assume to keep-alive by default
|
||||
, mKeepAliveMask(1)
|
||||
, mWriteDone(0)
|
||||
@ -97,7 +98,7 @@ nsHttpConnection::Init(nsHttpConnectionInfo *info, PRUint16 maxHangTime)
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
// called from any thread, with the connection lock held
|
||||
// called from any thread, without the connection lock held
|
||||
nsresult
|
||||
nsHttpConnection::SetTransaction(nsAHttpTransaction *transaction,
|
||||
PRUint8 caps)
|
||||
@ -186,6 +187,8 @@ nsHttpConnection::OnHeadersAvailable(nsAHttpTransaction *trans,
|
||||
if (!val)
|
||||
val = responseHead->PeekHeader(nsHttp::Proxy_Connection);
|
||||
|
||||
mServerVersion = responseHead->Version();
|
||||
|
||||
if ((responseHead->Version() < NS_HTTP_VERSION_1_1) ||
|
||||
(requestHead->Version() < NS_HTTP_VERSION_1_1)) {
|
||||
// HTTP/1.0 connections are by default NOT persistent
|
||||
|
@ -77,6 +77,7 @@ public:
|
||||
// called to cause the underlying socket to start speaking SSL
|
||||
nsresult ProxyStepUp();
|
||||
|
||||
PRBool SupportsPipelining() { return mServerVersion > NS_HTTP_VERSION_1_0; }
|
||||
PRBool IsKeepAlive() { return mKeepAliveMask && mKeepAlive; }
|
||||
PRBool CanReuse(); // can this connection be reused?
|
||||
void DontReuse() { mKeepAliveMask = PR_FALSE;
|
||||
@ -94,7 +95,7 @@ public:
|
||||
void GetConnectionInfo(nsHttpConnectionInfo **ci) { NS_IF_ADDREF(*ci = mConnectionInfo); }
|
||||
void DropTransaction(nsAHttpTransaction *);
|
||||
PRBool IsPersistent() { return IsKeepAlive(); }
|
||||
nsresult PushBack(char *data, PRUint32 length) { return NS_OK; }
|
||||
nsresult PushBack(const char *data, PRUint32 length) { NS_NOTREACHED("PushBack"); return NS_ERROR_UNEXPECTED; }
|
||||
|
||||
private:
|
||||
nsresult ActivateConnection();
|
||||
@ -121,6 +122,8 @@ private:
|
||||
PRUint16 mMaxHangTime; // max download time before dropping keep-alive status
|
||||
PRUint16 mIdleTimeout; // value of keep-alive: timeout=
|
||||
|
||||
PRUint8 mServerVersion;
|
||||
|
||||
PRPackedBool mKeepAlive;
|
||||
PRPackedBool mKeepAliveMask;
|
||||
PRPackedBool mWriteDone;
|
||||
|
@ -34,6 +34,7 @@
|
||||
#include "nsHttpResponseHead.h"
|
||||
#include "nsHttpTransaction.h"
|
||||
#include "nsHttpAuthCache.h"
|
||||
#include "nsHttpPipeline.h"
|
||||
#include "nsIHttpChannel.h"
|
||||
#include "nsIHttpNotify.h"
|
||||
#include "nsIURL.h"
|
||||
@ -111,6 +112,7 @@ nsHttpHandler::nsHttpHandler()
|
||||
, mMaxConnectionsPerServer(8)
|
||||
, mMaxPersistentConnectionsPerServer(2)
|
||||
, mMaxPersistentConnectionsPerProxy(4)
|
||||
, mMaxPipelinedRequests(2)
|
||||
, mRedirectionLimit(10)
|
||||
, mLastUniqueID(NowInSeconds())
|
||||
, mSessionStartTime(0)
|
||||
@ -409,7 +411,7 @@ nsHttpHandler::InitiateTransaction(nsHttpTransaction *trans,
|
||||
PR_Unlock(mConnectionLock);
|
||||
}
|
||||
else {
|
||||
rv = DispatchTransaction_Locked(trans, conn);
|
||||
rv = DispatchTransaction_Locked(trans, trans->Capabilities(), conn);
|
||||
NS_RELEASE(conn);
|
||||
}
|
||||
return rv;
|
||||
@ -420,9 +422,7 @@ nsresult
|
||||
nsHttpHandler::ReclaimConnection(nsHttpConnection *conn)
|
||||
{
|
||||
NS_ENSURE_ARG_POINTER(conn);
|
||||
#ifdef DEBUG
|
||||
NS_PRECONDITION(PR_GetCurrentThread() == NS_SOCKET_THREAD, "wrong thread");
|
||||
#endif
|
||||
NS_ASSERTION(PR_GetCurrentThread() == NS_SOCKET_THREAD, "wrong thread");
|
||||
|
||||
PRBool reusable = conn->CanReuse();
|
||||
|
||||
@ -472,9 +472,7 @@ nsresult
|
||||
nsHttpHandler::ProcessTransactionQ()
|
||||
{
|
||||
LOG(("nsHttpHandler::ProcessTransactionQ\n"));
|
||||
#ifdef DEBUG
|
||||
NS_PRECONDITION(PR_GetCurrentThread() == NS_SOCKET_THREAD, "wrong thread");
|
||||
#endif
|
||||
NS_ASSERTION(PR_GetCurrentThread() == NS_SOCKET_THREAD, "wrong thread");
|
||||
|
||||
PR_Lock(mConnectionLock);
|
||||
|
||||
@ -780,7 +778,8 @@ nsHttpHandler::EnqueueTransaction_Locked(nsHttpTransaction *trans,
|
||||
|
||||
// dispatch this transaction, return unlocked
|
||||
nsresult
|
||||
nsHttpHandler::DispatchTransaction_Locked(nsHttpTransaction *trans,
|
||||
nsHttpHandler::DispatchTransaction_Locked(nsAHttpTransaction *trans,
|
||||
PRUint8 transCaps,
|
||||
nsHttpConnection *conn)
|
||||
{
|
||||
nsresult rv;
|
||||
@ -800,7 +799,7 @@ nsHttpHandler::DispatchTransaction_Locked(nsHttpTransaction *trans,
|
||||
// we must not hold the connection lock while making the call to
|
||||
// SetTransaction, as it could lead to deadlocks.
|
||||
PR_Unlock(mConnectionLock);
|
||||
rv = conn->SetTransaction(trans, trans->Capabilities());
|
||||
rv = conn->SetTransaction(trans, transCaps);
|
||||
|
||||
if (NS_FAILED(rv)) {
|
||||
LOG(("nsHttpConnection::SetTransaction failed [rv=%x]\n", rv));
|
||||
@ -853,24 +852,47 @@ nsHttpHandler::ProcessTransactionQ_Locked()
|
||||
mTransactionQ.RemoveElementAt(i);
|
||||
|
||||
//
|
||||
// step 3: dispatch this transaction
|
||||
// step 3: determine if we can pipeline any other transactions along with
|
||||
// this transaction. if pipelining is disabled or there are no other
|
||||
// transactions that can be sent with this transaction, then simply send
|
||||
// this transaction.
|
||||
//
|
||||
nsresult rv = DispatchTransaction_Locked(pt->Transaction(), conn);
|
||||
nsAHttpTransaction *trans = pt->Transaction();
|
||||
PRUint8 caps = pt->Transaction()->Capabilities();
|
||||
nsPipelineEnqueueState pipelineState;
|
||||
if (conn->SupportsPipelining() &&
|
||||
caps & NS_HTTP_ALLOW_PIPELINING &&
|
||||
BuildPipeline_Locked(pipelineState,
|
||||
pt->Transaction(),
|
||||
pt->ConnectionInfo())) {
|
||||
// ok... let's rock!
|
||||
trans = pipelineState.Transaction();
|
||||
caps = pipelineState.TransactionCaps();
|
||||
NS_ASSERTION(trans, "no transaction");
|
||||
}
|
||||
|
||||
//
|
||||
// step 4: dispatch this transaction
|
||||
//
|
||||
nsresult rv = DispatchTransaction_Locked(trans, caps, conn);
|
||||
|
||||
// we're no longer inside mConnectionLock
|
||||
|
||||
//
|
||||
// step 4: handle errors / cleanup
|
||||
// step 5: handle errors / cleanup
|
||||
//
|
||||
if (NS_FAILED(rv)) {
|
||||
LOG((">> DispatchTransaction_Locked failed [rv=%x]\n", rv));
|
||||
nsAutoLock lock(mConnectionLock);
|
||||
// there must have been something wrong with the connection,
|
||||
// requeue... we'll try again later.
|
||||
if (caps & NS_HTTP_ALLOW_PIPELINING)
|
||||
PipelineFailed_Locked(pipelineState);
|
||||
mTransactionQ.AppendElement(pt);
|
||||
}
|
||||
else
|
||||
delete pt;
|
||||
pipelineState.Cleanup();
|
||||
NS_RELEASE(conn);
|
||||
}
|
||||
|
||||
@ -958,6 +980,80 @@ nsHttpHandler::DropConnections(nsVoidArray &connections)
|
||||
connections.Clear();
|
||||
}
|
||||
|
||||
PRBool
|
||||
nsHttpHandler::BuildPipeline_Locked(nsPipelineEnqueueState &state,
|
||||
nsHttpTransaction *firstTrans,
|
||||
nsHttpConnectionInfo *ci)
|
||||
{
|
||||
if (mMaxPipelinedRequests < 2)
|
||||
return PR_FALSE;
|
||||
|
||||
LOG(("BuildPipeline_Locked [trans=%x]\n", firstTrans));
|
||||
|
||||
//
|
||||
// need to search the pending transaction list for other transactions
|
||||
// that can be pipelined along with |firstTrans|.
|
||||
//
|
||||
nsresult rv = NS_ERROR_FAILURE; // by default, nothing to pipeline
|
||||
PRUint8 numAppended = 0;
|
||||
PRInt32 i = 0;
|
||||
while (i < mTransactionQ.Count()) {
|
||||
nsPendingTransaction *pt = (nsPendingTransaction *) mTransactionQ[i];
|
||||
if (pt->Transaction()->Capabilities() & (NS_HTTP_ALLOW_KEEPALIVE |
|
||||
NS_HTTP_ALLOW_PIPELINING) &&
|
||||
pt->ConnectionInfo()->Equals(ci)) {
|
||||
|
||||
//
|
||||
// ok, we can add this transaction to our pipeline
|
||||
//
|
||||
if (numAppended == 0) {
|
||||
rv = state.Init(firstTrans);
|
||||
if (NS_FAILED(rv)) break;
|
||||
}
|
||||
rv = state.AppendTransaction(pt);
|
||||
if (NS_FAILED(rv)) break;
|
||||
|
||||
//
|
||||
// ok, remove the transaction from the pending queue; next time
|
||||
// around the loop we'll still check the i-th element :-)
|
||||
//
|
||||
mTransactionQ.RemoveElementAt(i);
|
||||
|
||||
//
|
||||
// we may have reached the pipelined requests limit...
|
||||
//
|
||||
if (++numAppended == (mMaxPipelinedRequests - 1))
|
||||
break;
|
||||
}
|
||||
else
|
||||
i++; // advance to next pending transaction
|
||||
}
|
||||
if (NS_FAILED(rv)) {
|
||||
LOG((" unable to pipeline any transactions with this one\n"));
|
||||
state.Cleanup();
|
||||
return PR_FALSE;
|
||||
}
|
||||
LOG((" pipelined %u transactions\n", numAppended + 1));
|
||||
return PR_TRUE;
|
||||
}
|
||||
|
||||
void
|
||||
nsHttpHandler::PipelineFailed_Locked(nsPipelineEnqueueState &state)
|
||||
{
|
||||
if ((mMaxPipelinedRequests < 2) || !state.HasPipeline())
|
||||
return;
|
||||
|
||||
LOG(("PipelineFailed_Locked\n"));
|
||||
|
||||
// need to put any "appended" transactions back on the queue
|
||||
PRInt32 i = 0;
|
||||
while (i < state.NumAppendedTrans()) {
|
||||
mTransactionQ.AppendElement(state.GetAppendedTrans(i));
|
||||
i++;
|
||||
}
|
||||
state.DropAppendedTrans();
|
||||
}
|
||||
|
||||
void
|
||||
nsHttpHandler::BuildUserAgent()
|
||||
{
|
||||
@ -1338,10 +1434,11 @@ nsHttpHandler::PrefsChanged(nsIPrefBranch *prefs, const char *pref)
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
mPipelineMaxRequests = DEFAULT_PIPELINE_MAX_REQUESTS;
|
||||
rv = prefs->GetIntPref("network.http.pipelining.maxrequests", &mPipelineMaxRequests );
|
||||
*/
|
||||
if (PREF_CHANGED(HTTP_PREF("pipelining.maxrequests"))) {
|
||||
rv = prefs->GetIntPref(HTTP_PREF("pipelining.maxrequests"), &val);
|
||||
if (NS_SUCCEEDED(rv))
|
||||
mMaxPipelinedRequests = CLAMP(val, 1, NS_HTTP_MAX_PIPELINED_REQUESTS);
|
||||
}
|
||||
|
||||
if (PREF_CHANGED(HTTP_PREF("proxy.pipelining"))) {
|
||||
rv = prefs->GetBoolPref(HTTP_PREF("proxy.pipelining"), &cVar);
|
||||
@ -1971,6 +2068,8 @@ nsHttpHandler::Observe(nsISupports *subject,
|
||||
const char *topic,
|
||||
const PRUnichar *data)
|
||||
{
|
||||
LOG(("nsHttpHandler::Observe [topic=\"%s\")]\n", topic));
|
||||
|
||||
if (!nsCRT::strcmp(topic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID)) {
|
||||
nsCOMPtr<nsIPrefBranch> prefBranch = do_QueryInterface(subject);
|
||||
if (prefBranch)
|
||||
@ -2024,3 +2123,42 @@ nsPendingTransaction::~nsPendingTransaction()
|
||||
NS_RELEASE(mTransaction);
|
||||
NS_RELEASE(mConnectionInfo);
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// nsHttpHandler::nsPipelineEnqueueState
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
nsresult nsHttpHandler::
|
||||
nsPipelineEnqueueState::Init(nsHttpTransaction *firstTrans)
|
||||
{
|
||||
NS_ASSERTION(mPipeline == nsnull, "already initialized");
|
||||
|
||||
mPipeline = new nsHttpPipeline;
|
||||
if (!mPipeline)
|
||||
return NS_ERROR_OUT_OF_MEMORY;
|
||||
NS_ADDREF(mPipeline);
|
||||
|
||||
return mPipeline->Init(firstTrans);
|
||||
}
|
||||
|
||||
nsresult nsHttpHandler::
|
||||
nsPipelineEnqueueState::AppendTransaction(nsPendingTransaction *pt)
|
||||
{
|
||||
nsresult rv = mPipeline->AppendTransaction(pt->Transaction());
|
||||
if (NS_FAILED(rv)) return rv;
|
||||
|
||||
// remember this pending transaction object
|
||||
mAppendedTrans.AppendElement(pt);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
void nsHttpHandler::
|
||||
nsPipelineEnqueueState::Cleanup()
|
||||
{
|
||||
NS_IF_RELEASE(mPipeline);
|
||||
|
||||
// free any appended pending transaction objects
|
||||
for (PRInt32 i=0; i < mAppendedTrans.Count(); i++)
|
||||
delete GetAppendedTrans(i);
|
||||
mAppendedTrans.Clear();
|
||||
}
|
||||
|
@ -25,6 +25,7 @@
|
||||
#define nsHttpHandler_h__
|
||||
|
||||
#include "nsHttp.h"
|
||||
#include "nsHttpPipeline.h"
|
||||
#include "nsIHttpProtocolHandler.h"
|
||||
#include "nsIProtocolProxyService.h"
|
||||
#include "nsIIOService.h"
|
||||
@ -49,6 +50,7 @@ class nsHttpConnectionInfo;
|
||||
class nsHttpHeaderArray;
|
||||
class nsHttpTransaction;
|
||||
class nsHttpAuthCache;
|
||||
class nsAHttpTransaction;
|
||||
class nsIHttpChannel;
|
||||
class nsIPrefBranch;
|
||||
|
||||
@ -147,7 +149,7 @@ public:
|
||||
// Called by the channel once headers are available
|
||||
nsresult OnExamineResponse(nsIHttpChannel *);
|
||||
|
||||
private:
|
||||
public: /* internal */
|
||||
//
|
||||
// Transactions that have not yet been assigned to a connection are kept
|
||||
// in a queue of nsPendingTransaction objects. nsPendingTransaction
|
||||
@ -171,18 +173,54 @@ private:
|
||||
PRPackedBool mBusy;
|
||||
};
|
||||
|
||||
//
|
||||
// Data structure used to hold information used during the construction of
|
||||
// a transaction pipeline.
|
||||
//
|
||||
class nsPipelineEnqueueState
|
||||
{
|
||||
public:
|
||||
nsPipelineEnqueueState() : mPipeline(nsnull) {}
|
||||
~nsPipelineEnqueueState() { Cleanup(); }
|
||||
|
||||
nsresult Init(nsHttpTransaction *);
|
||||
nsresult AppendTransaction(nsPendingTransaction *);
|
||||
void Cleanup();
|
||||
|
||||
nsAHttpTransaction *Transaction() { return mPipeline; }
|
||||
PRUint8 TransactionCaps() { return NS_HTTP_ALLOW_KEEPALIVE | NS_HTTP_ALLOW_PIPELINING; }
|
||||
PRBool HasPipeline() { return (mPipeline != nsnull); }
|
||||
nsPendingTransaction *GetAppendedTrans(PRInt32 i) { return (nsPendingTransaction *) mAppendedTrans[i]; }
|
||||
PRInt32 NumAppendedTrans() { return mAppendedTrans.Count(); }
|
||||
void DropAppendedTrans() { mAppendedTrans.Clear(); }
|
||||
|
||||
private:
|
||||
nsHttpPipeline *mPipeline;
|
||||
nsAutoVoidArray mAppendedTrans;
|
||||
};
|
||||
|
||||
private:
|
||||
|
||||
//
|
||||
// Connection management methods
|
||||
//
|
||||
nsresult GetConnection_Locked(nsHttpConnectionInfo *, PRUint8 caps, nsHttpConnection **);
|
||||
nsresult EnqueueTransaction_Locked(nsHttpTransaction *, nsHttpConnectionInfo *);
|
||||
nsresult DispatchTransaction_Locked(nsHttpTransaction *, nsHttpConnection *); // unlocked on return
|
||||
nsresult DispatchTransaction_Locked(nsAHttpTransaction *, PRUint8 caps, nsHttpConnection *); // unlocked on return
|
||||
void ProcessTransactionQ_Locked(); // unlocked on return
|
||||
PRBool AtActiveConnectionLimit_Locked(nsHttpConnectionInfo *, PRUint8 caps);
|
||||
nsresult RemovePendingTransaction_Locked(nsHttpTransaction *);
|
||||
|
||||
void DropConnections(nsVoidArray &);
|
||||
|
||||
//
|
||||
// Pipelining methods - these may fail silently, and that's ok!
|
||||
//
|
||||
PRBool BuildPipeline_Locked(nsPipelineEnqueueState &,
|
||||
nsHttpTransaction *,
|
||||
nsHttpConnectionInfo *);
|
||||
void PipelineFailed_Locked(nsPipelineEnqueueState &);
|
||||
|
||||
//
|
||||
// Useragent/prefs helper methods
|
||||
//
|
||||
@ -233,6 +271,7 @@ private:
|
||||
PRUint8 mMaxConnectionsPerServer;
|
||||
PRUint8 mMaxPersistentConnectionsPerServer;
|
||||
PRUint8 mMaxPersistentConnectionsPerProxy;
|
||||
PRUint8 mMaxPipelinedRequests;
|
||||
|
||||
PRUint8 mRedirectionLimit;
|
||||
|
||||
|
682
netwerk/protocol/http/src/nsHttpPipeline.cpp
Normal file
682
netwerk/protocol/http/src/nsHttpPipeline.cpp
Normal file
@ -0,0 +1,682 @@
|
||||
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
||||
/*
|
||||
* The contents of this file are subject to the Mozilla Public
|
||||
* License Version 1.1 (the "License"); you may not use this file
|
||||
* except in compliance with the License. You may obtain a copy of
|
||||
* the License at http://www.mozilla.org/MPL/
|
||||
*
|
||||
* Software distributed under the License is distributed on an "AS
|
||||
* IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
|
||||
* implied. See the License for the specific language governing
|
||||
* rights and limitations under the License.
|
||||
*
|
||||
* The Original Code is Mozilla.
|
||||
*
|
||||
* The Initial Developer of the Original Code is Netscape
|
||||
* Communications. Portions created by Netscape Communications are
|
||||
* Copyright (C) 2001 by Netscape Communications. All
|
||||
* Rights Reserved.
|
||||
*
|
||||
* Contributor(s):
|
||||
* Darin Fisher <darin@netscape.com> (original author)
|
||||
*/
|
||||
|
||||
#include "nsHttp.h"
|
||||
#include "nsHttpPipeline.h"
|
||||
#include "nsIRequest.h"
|
||||
#include "nsISocketTransportService.h"
|
||||
#include "nsIStringStream.h"
|
||||
#include "nsIPipe.h"
|
||||
#include "nsCOMPtr.h"
|
||||
#include "nsComponentManagerUtils.h"
|
||||
#include "nsAutoLock.h"
|
||||
|
||||
#ifdef DEBUG
|
||||
#include "prthread.h"
|
||||
// defined by the socket transport service while active
|
||||
extern PRThread *NS_SOCKET_THREAD;
|
||||
#endif
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// nsHttpPipeline::nsInputStreamWrapper
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
nsHttpPipeline::
|
||||
nsInputStreamWrapper::nsInputStreamWrapper(const char *data, PRUint32 dataLen)
|
||||
: mData(data)
|
||||
, mDataLen(dataLen)
|
||||
, mDataPos(0)
|
||||
{
|
||||
}
|
||||
|
||||
nsHttpPipeline::
|
||||
nsInputStreamWrapper::~nsInputStreamWrapper()
|
||||
{
|
||||
}
|
||||
|
||||
// this thing is going to be allocated on the stack
|
||||
NS_IMETHODIMP_(nsrefcnt) nsHttpPipeline::
|
||||
nsInputStreamWrapper::AddRef()
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP_(nsrefcnt) nsHttpPipeline::
|
||||
nsInputStreamWrapper::Release()
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
NS_IMPL_THREADSAFE_QUERY_INTERFACE1(nsHttpPipeline::nsInputStreamWrapper, nsIInputStream)
|
||||
|
||||
NS_IMETHODIMP nsHttpPipeline::
|
||||
nsInputStreamWrapper::Close()
|
||||
{
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP nsHttpPipeline::
|
||||
nsInputStreamWrapper::Available(PRUint32 *result)
|
||||
{
|
||||
*result = (mDataLen - mDataPos);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
static NS_METHOD
|
||||
nsWriteToRawBuffer(nsIInputStream *inStr,
|
||||
void *closure,
|
||||
const char *fromRawSegment,
|
||||
PRUint32 offset,
|
||||
PRUint32 count,
|
||||
PRUint32 *writeCount)
|
||||
{
|
||||
char *toBuf = (char *) closure;
|
||||
memcpy(toBuf + offset, fromRawSegment, count);
|
||||
*writeCount = count;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP nsHttpPipeline::
|
||||
nsInputStreamWrapper::Read(char *buf, PRUint32 count, PRUint32 *countRead)
|
||||
{
|
||||
return ReadSegments(nsWriteToRawBuffer, buf, count, countRead);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP nsHttpPipeline::
|
||||
nsInputStreamWrapper::ReadSegments(nsWriteSegmentFun writer,
|
||||
void *closure,
|
||||
PRUint32 count,
|
||||
PRUint32 *countRead)
|
||||
{
|
||||
nsresult rv;
|
||||
PRUint32 maxCount = mDataLen - mDataPos;
|
||||
if (count > maxCount)
|
||||
count = maxCount;
|
||||
|
||||
// here's the code that distinguishes this implementation from other
|
||||
// string stream implementations. normally, we'd return NS_OK to
|
||||
// signify EOF, but we need to return NS_BASE_STREAM_WOULD_BLOCK to
|
||||
// keep the nsStreamListenerProxy code happy.
|
||||
if (count == 0) {
|
||||
*countRead = 0;
|
||||
return NS_OK;
|
||||
}
|
||||
//if (count == 0)
|
||||
// return NS_BASE_STREAM_WOULD_BLOCK;
|
||||
|
||||
rv = writer(this, closure, mData + mDataPos, 0, count, countRead);
|
||||
if (NS_SUCCEEDED(rv))
|
||||
mDataPos += *countRead;
|
||||
return rv;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP nsHttpPipeline::
|
||||
nsInputStreamWrapper::IsNonBlocking(PRBool *result)
|
||||
{
|
||||
*result = PR_TRUE;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// nsHttpPipeline <public>
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
nsHttpPipeline::nsHttpPipeline()
|
||||
: mConnection(nsnull)
|
||||
, mNumTrans(0)
|
||||
, mCurrentReader(-1)
|
||||
, mLock(nsnull)
|
||||
, mStatus(NS_OK)
|
||||
{
|
||||
NS_INIT_ISUPPORTS();
|
||||
|
||||
memset(mTransactionQ, 0, sizeof(PRUint32) * NS_HTTP_MAX_PIPELINED_REQUESTS);
|
||||
memset(mTransactionFlags, 0, sizeof(PRUint32) * NS_HTTP_MAX_PIPELINED_REQUESTS);
|
||||
}
|
||||
|
||||
nsHttpPipeline::~nsHttpPipeline()
|
||||
{
|
||||
NS_IF_RELEASE(mConnection);
|
||||
|
||||
for (PRInt8 i=0; i<mNumTrans; i++) {
|
||||
if (mTransactionQ[i]) {
|
||||
nsAHttpTransaction *trans = mTransactionQ[i];
|
||||
NS_RELEASE(trans);
|
||||
}
|
||||
}
|
||||
|
||||
if (mLock)
|
||||
PR_DestroyLock(mLock);
|
||||
}
|
||||
|
||||
// called while inside nsHttpHandler::mConnectionLock
|
||||
nsresult
|
||||
nsHttpPipeline::Init(nsAHttpTransaction *firstTrans)
|
||||
{
|
||||
LOG(("nsHttpPipeline::Init [this=%x trans=%x]\n", this, firstTrans));
|
||||
|
||||
NS_ASSERTION(!mConnection, "already initialized");
|
||||
|
||||
mLock = PR_NewLock();
|
||||
if (!mLock)
|
||||
return NS_ERROR_OUT_OF_MEMORY;
|
||||
|
||||
NS_ADDREF(firstTrans);
|
||||
mTransactionQ[0] = firstTrans;
|
||||
mNumTrans++;
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
// called while inside nsHttpHandler::mConnectionLock
|
||||
nsresult
|
||||
nsHttpPipeline::AppendTransaction(nsAHttpTransaction *trans)
|
||||
{
|
||||
LOG(("nsHttpPipeline::AppendTransaction [this=%x trans=%x]\n", this, trans));
|
||||
|
||||
NS_ASSERTION(!mConnection, "already initialized");
|
||||
NS_ASSERTION(mNumTrans < NS_HTTP_MAX_PIPELINED_REQUESTS, "too many transactions");
|
||||
|
||||
NS_ADDREF(trans);
|
||||
mTransactionQ[mNumTrans++] = trans;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// nsHttpPipeline::nsISupports
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
NS_IMPL_THREADSAFE_ADDREF(nsHttpPipeline)
|
||||
NS_IMPL_THREADSAFE_RELEASE(nsHttpPipeline)
|
||||
|
||||
// multiple inheritance fun :-)
|
||||
NS_INTERFACE_MAP_BEGIN(nsHttpPipeline)
|
||||
NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsAHttpConnection)
|
||||
NS_INTERFACE_MAP_END
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// nsHttpPipeline::nsAHttpConnection
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
// called on the socket thread
|
||||
nsresult
|
||||
nsHttpPipeline::OnHeadersAvailable(nsAHttpTransaction *trans,
|
||||
nsHttpRequestHead *requestHead,
|
||||
nsHttpResponseHead *responseHead,
|
||||
PRBool *reset)
|
||||
{
|
||||
LOG(("nsHttpPipeline::OnHeadersAvailable [this=%x]\n", this));
|
||||
|
||||
NS_ASSERTION(mConnection, "no connection");
|
||||
// trans has now received its response headers; forward to the real connection
|
||||
return mConnection->OnHeadersAvailable(trans, requestHead, responseHead, reset);
|
||||
}
|
||||
|
||||
// called on any thread
|
||||
nsresult
|
||||
nsHttpPipeline::OnTransactionComplete(nsAHttpTransaction *trans, nsresult status)
|
||||
{
|
||||
LOG(("nsHttpPipeline::OnTransactionComplete [this=%x trans=%x status=%x]\n",
|
||||
this, trans, status));
|
||||
|
||||
// called either from nsHttpTransaction::HandleContent (socket thread)
|
||||
// or from nsHttpHandler::CancelTransaction (any thread)
|
||||
|
||||
PRBool mustCancel = PR_FALSE, stopTrans = PR_FALSE;
|
||||
{
|
||||
nsAutoLock lock(mLock);
|
||||
|
||||
PRInt8 transIndex = LocateTransaction_Locked(trans);
|
||||
NS_ASSERTION(transIndex != -1, "unknown transaction");
|
||||
|
||||
mTransactionFlags[transIndex] = eTransactionComplete;
|
||||
|
||||
if (NS_FAILED(status)) {
|
||||
mStatus = status;
|
||||
|
||||
// don't bother waiting for a connection to be established if the
|
||||
// first transaction has been canceled.
|
||||
if (transIndex == 0)
|
||||
mustCancel = PR_TRUE;
|
||||
// go ahead and kill off the transaction if it hasn't started
|
||||
// reading yet.
|
||||
if (transIndex > mCurrentReader) {
|
||||
stopTrans = PR_TRUE;
|
||||
DropTransaction_Locked(transIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (stopTrans)
|
||||
trans->OnStopTransaction(status);
|
||||
|
||||
if (mustCancel) {
|
||||
NS_ASSERTION(mConnection, "no connection");
|
||||
mConnection->OnTransactionComplete(this, status);
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
// not called on the socket thread
|
||||
nsresult
|
||||
nsHttpPipeline::OnSuspend()
|
||||
{
|
||||
LOG(("nsHttpPipeline::OnSuspend [this=%x]\n", this));
|
||||
|
||||
NS_ASSERTION(mConnection, "no connection");
|
||||
return mConnection->OnSuspend();
|
||||
}
|
||||
|
||||
// not called on the socket thread
|
||||
nsresult
|
||||
nsHttpPipeline::OnResume()
|
||||
{
|
||||
LOG(("nsHttpPipeline::OnResume [this=%x]\n", this));
|
||||
|
||||
NS_ASSERTION(mConnection, "no connection");
|
||||
return mConnection->OnResume();
|
||||
}
|
||||
|
||||
// called on any thread
|
||||
void
|
||||
nsHttpPipeline::GetConnectionInfo(nsHttpConnectionInfo **result)
|
||||
{
|
||||
LOG(("nsHttpPipeline::GetConnectionInfo [this=%x]\n", this));
|
||||
|
||||
NS_ASSERTION(mConnection, "no connection");
|
||||
mConnection->GetConnectionInfo(result);
|
||||
}
|
||||
|
||||
// called on the socket thread
|
||||
void
|
||||
nsHttpPipeline::DropTransaction(nsAHttpTransaction *trans)
|
||||
{
|
||||
LOG(("nsHttpPipeline::DropTransaction [this=%x trans=%x]\n", this, trans));
|
||||
|
||||
NS_ASSERTION(mConnection, "no connection");
|
||||
|
||||
// clear the transaction from our queue
|
||||
{
|
||||
nsAutoLock lock(mLock);
|
||||
|
||||
PRInt8 transIndex = LocateTransaction_Locked(trans);
|
||||
if (transIndex == -1)
|
||||
return;
|
||||
|
||||
DropTransaction_Locked(transIndex);
|
||||
|
||||
mStatus = NS_ERROR_NET_RESET;
|
||||
}
|
||||
|
||||
// Assuming DropTransaction is called in response to a dead socket connection...
|
||||
mConnection->OnTransactionComplete(this, NS_ERROR_NET_RESET);
|
||||
}
|
||||
|
||||
// called on any thread
|
||||
PRBool
|
||||
nsHttpPipeline::IsPersistent()
|
||||
{
|
||||
return PR_TRUE;
|
||||
}
|
||||
|
||||
// called on the socket thread
|
||||
nsresult
|
||||
nsHttpPipeline::PushBack(const char *data, PRUint32 length)
|
||||
{
|
||||
LOG(("nsHttpPipeline::PushBack [this=%x len=%u]\n", this, length));
|
||||
|
||||
nsInputStreamWrapper readable(data, length);
|
||||
|
||||
return OnDataReadable(&readable);
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// nsHttpPipeline::nsAHttpConnection
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
void
|
||||
nsHttpPipeline::SetConnection(nsAHttpConnection *conn)
|
||||
{
|
||||
LOG(("nsHttpPipeline::SetConnection [this=%x conn=%x]\n", this, conn));
|
||||
|
||||
NS_ASSERTION(!mConnection, "already have a connection");
|
||||
NS_IF_ADDREF(mConnection = conn);
|
||||
|
||||
// no need to be inside the lock
|
||||
for (PRInt8 i=0; i<mNumTrans; ++i) {
|
||||
NS_ASSERTION(mTransactionQ[i], "no transaction");
|
||||
mTransactionQ[i]->SetConnection(this);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
nsHttpPipeline::SetSecurityInfo(nsISupports *securityInfo)
|
||||
{
|
||||
LOG(("nsHttpPipeline::SetSecurityInfo [this=%x]\n", this));
|
||||
|
||||
// set security info on each transaction
|
||||
nsAutoLock lock(mLock);
|
||||
for (PRInt8 i=0; i<mNumTrans; ++i) {
|
||||
if (mTransactionQ[i])
|
||||
mTransactionQ[i]->SetSecurityInfo(securityInfo);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
nsHttpPipeline::GetNotificationCallbacks(nsIInterfaceRequestor **result)
|
||||
{
|
||||
LOG(("nsHttpPipeline::GetNotificationCallbacks [this=%x]\n", this));
|
||||
|
||||
// return notification callbacks from first transaction
|
||||
nsAutoLock lock(mLock);
|
||||
if (mTransactionQ[0])
|
||||
mTransactionQ[0]->GetNotificationCallbacks(result);
|
||||
else
|
||||
*result = nsnull;
|
||||
}
|
||||
|
||||
PRUint32
|
||||
nsHttpPipeline::GetRequestSize()
|
||||
{
|
||||
LOG(("nsHttpPipeline::GetRequestSize [this=%x]\n", this));
|
||||
|
||||
nsAutoLock lock(mLock);
|
||||
return GetRequestSize_Locked();
|
||||
}
|
||||
|
||||
// called on the socket thread
|
||||
nsresult
|
||||
nsHttpPipeline::OnDataWritable(nsIOutputStream *stream)
|
||||
{
|
||||
LOG(("nsHttpPipeline::OnDataWritable [this=%x]\n", this));
|
||||
|
||||
NS_ASSERTION(PR_GetCurrentThread() == NS_SOCKET_THREAD, "wrong thread");
|
||||
|
||||
nsresult rv;
|
||||
if (!mRequestData) {
|
||||
nsAutoLock lock(mLock);
|
||||
|
||||
// check for early cancelation
|
||||
if (NS_FAILED(mStatus))
|
||||
return mStatus;
|
||||
|
||||
// allocate a pipe for the request data
|
||||
PRUint32 size = GetRequestSize_Locked();
|
||||
nsCOMPtr<nsIOutputStream> outputStream;
|
||||
rv = NS_NewPipe(getter_AddRefs(mRequestData),
|
||||
getter_AddRefs(outputStream),
|
||||
size, size, PR_TRUE, PR_TRUE);
|
||||
if (NS_FAILED(rv)) return rv;
|
||||
|
||||
// fill the pipe
|
||||
for (PRInt32 i=0; i<mNumTrans; ++i) {
|
||||
NS_ASSERTION(mTransactionQ[i], "no transaction");
|
||||
while (1) {
|
||||
rv = mTransactionQ[i]->OnDataWritable(outputStream);
|
||||
if (rv == NS_BASE_STREAM_CLOSED)
|
||||
break; // advance to next transaction
|
||||
if (NS_FAILED(rv))
|
||||
return rv; // something bad happened!!
|
||||
// else, there's more to write (the transaction may be
|
||||
// writing in small chunks).
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
nsAutoLock lock(mLock);
|
||||
|
||||
// check for early cancelation... (important for slow connections)
|
||||
// only abort if we haven't started reading
|
||||
if (NS_FAILED(mStatus) && (mCurrentReader == -1))
|
||||
return mStatus;
|
||||
}
|
||||
|
||||
// find out how much data still needs to be written, and write it
|
||||
PRUint32 n = 0;
|
||||
rv = mRequestData->Available(&n);
|
||||
if (NS_FAILED(rv)) return rv;
|
||||
|
||||
if (n > 0)
|
||||
return stream->WriteFrom(mRequestData, NS_HTTP_BUFFER_SIZE, &n);
|
||||
|
||||
// if nothing to write, then signal EOF
|
||||
return NS_BASE_STREAM_CLOSED;
|
||||
}
|
||||
|
||||
// called on the socket thread (may be called recursively)
|
||||
nsresult
|
||||
nsHttpPipeline::OnDataReadable(nsIInputStream *stream)
|
||||
{
|
||||
LOG(("nsHttpPipeline::OnDataReadable [this=%x]\n", this));
|
||||
|
||||
NS_ASSERTION(PR_GetCurrentThread() == NS_SOCKET_THREAD, "wrong thread");
|
||||
|
||||
nsresult rv;
|
||||
nsAutoLock lock(mLock);
|
||||
|
||||
if (mCurrentReader == -1)
|
||||
mCurrentReader = 0;
|
||||
|
||||
while (1) {
|
||||
nsAHttpTransaction *reader = mTransactionQ[mCurrentReader];
|
||||
// the reader may be NULL if it has already completed.
|
||||
if (!reader || (mTransactionFlags[mCurrentReader] & eTransactionComplete)) {
|
||||
// advance to next reader
|
||||
if (++mCurrentReader == mNumTrans) {
|
||||
mCurrentReader = -1;
|
||||
break;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// remember the index of this reader
|
||||
PRUint32 readerIndex = mCurrentReader;
|
||||
PRUint32 bytesRemaining = 0;
|
||||
|
||||
mTransactionFlags[readerIndex] |= eTransactionReading;
|
||||
|
||||
// cannot hold lock while calling OnDataReadable... must also ensure
|
||||
// that |reader| doesn't dissappear on us.
|
||||
nsCOMPtr<nsISupports> readerDeathGrip(reader);
|
||||
PR_Unlock(mLock);
|
||||
|
||||
rv = reader->OnDataReadable(stream);
|
||||
|
||||
if (NS_SUCCEEDED(rv))
|
||||
rv = stream->Available(&bytesRemaining);
|
||||
|
||||
PR_Lock(mLock);
|
||||
|
||||
if (NS_FAILED(rv)) return rv;
|
||||
|
||||
// the reader may have completed...
|
||||
if (mTransactionFlags[readerIndex] & eTransactionComplete) {
|
||||
reader->OnStopTransaction(reader->Status());
|
||||
DropTransaction_Locked(readerIndex);
|
||||
}
|
||||
|
||||
// the pipeline may have completed...
|
||||
if (NS_FAILED(mStatus) || IsDone_Locked()) {
|
||||
NS_ASSERTION(mConnection, "no connection");
|
||||
mConnection->OnTransactionComplete(this, mStatus);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
// otherwise, if there is nothing left in the stream, then unwind...
|
||||
if (bytesRemaining == 0)
|
||||
return NS_OK;
|
||||
|
||||
// PushBack may have been called during the call to OnDataReadable, so
|
||||
// we cannot depend on |reader| pointing to |mCurrentReader| anymore.
|
||||
// loop around, and re-acquire |reader|.
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
// called on any thread
|
||||
nsresult
|
||||
nsHttpPipeline::OnStopTransaction(nsresult status)
|
||||
{
|
||||
LOG(("nsHttpPipeline::OnStopTransaction [this=%x status=%x]\n", this, status));
|
||||
|
||||
// called either from nsHttpHandler::CancelTransaction (mConnection == nsnull)
|
||||
// or from nsHttpConnection::OnStopRequest (on the socket thread)
|
||||
|
||||
if (mConnection) {
|
||||
NS_ASSERTION(PR_GetCurrentThread() == NS_SOCKET_THREAD, "wrong thread");
|
||||
nsAutoLock lock(mLock);
|
||||
NS_ASSERTION(mStatus == status, "unexpected status");
|
||||
// reset any transactions that haven't already completed.
|
||||
//
|
||||
// normally, we'd expect the current reader to have completed already;
|
||||
// however, if the server happens to switch to HTTP/1.0 and not send a
|
||||
// Content-Length for one of the pipelined responses (yes, it does
|
||||
// happen!!), then we'll need to be sure to not reset the corresponding
|
||||
// transaction.
|
||||
for (PRInt8 i=0; i<mNumTrans; ++i) {
|
||||
if (mTransactionQ[i]) {
|
||||
nsAHttpTransaction *trans = mTransactionQ[i];
|
||||
NS_ADDREF(trans);
|
||||
|
||||
PRBool mustReset = !(mTransactionFlags[i] & eTransactionReading);
|
||||
|
||||
DropTransaction_Locked(i);
|
||||
|
||||
PR_Unlock(mLock);
|
||||
if (mustReset)
|
||||
// this will end up calling our DropTransaction, which will return
|
||||
// early since we have already dropped this transaction. this is
|
||||
// important since it allows us to distinguish what we are doing
|
||||
// here from premature EOF detection.
|
||||
trans->OnStopTransaction(NS_ERROR_NET_RESET);
|
||||
else
|
||||
trans->OnStopTransaction(status);
|
||||
PR_Lock(mLock);
|
||||
|
||||
NS_RELEASE(trans);
|
||||
}
|
||||
}
|
||||
mCurrentReader = -1;
|
||||
mNumTrans = 0;
|
||||
}
|
||||
else {
|
||||
NS_ASSERTION(NS_FAILED(status), "unexpected cancelation status");
|
||||
NS_ASSERTION(mCurrentReader == -1, "unexpected reader");
|
||||
for (PRInt8 i=0; i<mNumTrans; ++i) {
|
||||
NS_ASSERTION(mTransactionQ[i], "no transaction");
|
||||
mTransactionQ[i]->OnStopTransaction(status);
|
||||
DropTransaction_Locked(i);
|
||||
}
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
// called on the socket thread
|
||||
void
|
||||
nsHttpPipeline::OnStatus(nsresult status, const PRUnichar *statusText)
|
||||
{
|
||||
LOG(("nsHttpPipeline::OnStatus [this=%x status=%x]\n", this, status));
|
||||
|
||||
nsAutoLock lock(mLock);
|
||||
switch (status) {
|
||||
case NS_NET_STATUS_RECEIVING_FROM:
|
||||
// forward this only to the transaction currently recieving data
|
||||
if (mCurrentReader != -1 && mTransactionQ[mCurrentReader])
|
||||
mTransactionQ[mCurrentReader]->OnStatus(status, statusText);
|
||||
break;
|
||||
default:
|
||||
// forward other notifications to all transactions
|
||||
for (PRInt8 i=0; i<mNumTrans; ++i) {
|
||||
if (mTransactionQ[i])
|
||||
mTransactionQ[i]->OnStatus(status, statusText);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
PRBool
|
||||
nsHttpPipeline::IsDone()
|
||||
{
|
||||
LOG(("nsHttpPipeline::IsDone [this=%x]\n", this));
|
||||
|
||||
nsAutoLock lock(mLock);
|
||||
return IsDone_Locked();
|
||||
}
|
||||
|
||||
nsresult
|
||||
nsHttpPipeline::Status()
|
||||
{
|
||||
LOG(("nsHttpPipeline::Status [this=%x status=%x]\n", this, mStatus));
|
||||
|
||||
return mStatus;
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// nsHttpPipeline <private>
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
PRBool
|
||||
nsHttpPipeline::IsDone_Locked()
|
||||
{
|
||||
// done if all of the transactions are null
|
||||
for (PRInt8 i=0; i<mNumTrans; ++i) {
|
||||
if (mTransactionQ[i])
|
||||
return PR_FALSE;
|
||||
}
|
||||
return PR_TRUE;
|
||||
}
|
||||
|
||||
PRInt8
|
||||
nsHttpPipeline::LocateTransaction_Locked(nsAHttpTransaction *trans)
|
||||
{
|
||||
for (PRInt8 i=0; i<mNumTrans; ++i) {
|
||||
if (mTransactionQ[i] == trans)
|
||||
return i;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
void
|
||||
nsHttpPipeline::DropTransaction_Locked(PRInt8 i)
|
||||
{
|
||||
mTransactionFlags[i] = 0;
|
||||
NS_RELEASE(mTransactionQ[i]);
|
||||
}
|
||||
|
||||
PRUint32
|
||||
nsHttpPipeline::GetRequestSize_Locked()
|
||||
{
|
||||
PRUint32 size = 0;
|
||||
for (PRInt8 i=0; i<mNumTrans; ++i)
|
||||
size += mTransactionQ[i]->GetRequestSize();
|
||||
LOG((" request-size=%u\n", size));
|
||||
return size;
|
||||
}
|
111
netwerk/protocol/http/src/nsHttpPipeline.h
Normal file
111
netwerk/protocol/http/src/nsHttpPipeline.h
Normal file
@ -0,0 +1,111 @@
|
||||
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
||||
/*
|
||||
* The contents of this file are subject to the Mozilla Public
|
||||
* License Version 1.1 (the "License"); you may not use this file
|
||||
* except in compliance with the License. You may obtain a copy of
|
||||
* the License at http://www.mozilla.org/MPL/
|
||||
*
|
||||
* Software distributed under the License is distributed on an "AS
|
||||
* IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
|
||||
* implied. See the License for the specific language governing
|
||||
* rights and limitations under the License.
|
||||
*
|
||||
* The Original Code is Mozilla.
|
||||
*
|
||||
* The Initial Developer of the Original Code is Netscape
|
||||
* Communications. Portions created by Netscape Communications are
|
||||
* Copyright (C) 2001 by Netscape Communications. All
|
||||
* Rights Reserved.
|
||||
*
|
||||
* Contributor(s):
|
||||
* Darin Fisher <darin@netscape.com> (original author)
|
||||
*/
|
||||
|
||||
#ifndef nsHttpPipeline_h__
|
||||
#define nsHttpPipeline_h__
|
||||
|
||||
#include "nsHttp.h"
|
||||
#include "nsAHttpConnection.h"
|
||||
#include "nsAHttpTransaction.h"
|
||||
#include "nsIInputStream.h"
|
||||
#include "nsCOMPtr.h"
|
||||
|
||||
class nsHttpPipeline : public nsAHttpConnection
|
||||
, public nsAHttpTransaction
|
||||
{
|
||||
public:
|
||||
NS_DECL_ISUPPORTS
|
||||
|
||||
nsHttpPipeline();
|
||||
virtual ~nsHttpPipeline();
|
||||
|
||||
nsresult Init(nsAHttpTransaction *);
|
||||
nsresult AppendTransaction(nsAHttpTransaction *);
|
||||
|
||||
// nsAHttpConnection methods:
|
||||
nsresult OnHeadersAvailable(nsAHttpTransaction *, nsHttpRequestHead *, nsHttpResponseHead *, PRBool *reset);
|
||||
nsresult OnTransactionComplete(nsAHttpTransaction *, nsresult status);
|
||||
nsresult OnSuspend();
|
||||
nsresult OnResume();
|
||||
void GetConnectionInfo(nsHttpConnectionInfo **);
|
||||
void DropTransaction(nsAHttpTransaction *);
|
||||
PRBool IsPersistent();
|
||||
nsresult PushBack(const char *, PRUint32);
|
||||
|
||||
// nsAHttpTransaction methods:
|
||||
void SetConnection(nsAHttpConnection *);
|
||||
void SetSecurityInfo(nsISupports *);
|
||||
void GetNotificationCallbacks(nsIInterfaceRequestor **);
|
||||
PRUint32 GetRequestSize();
|
||||
nsresult OnDataWritable(nsIOutputStream *);
|
||||
nsresult OnDataReadable(nsIInputStream *);
|
||||
nsresult OnStopTransaction(nsresult status);
|
||||
void OnStatus(nsresult status, const PRUnichar *statusText);
|
||||
PRBool IsDone();
|
||||
nsresult Status();
|
||||
|
||||
private:
|
||||
//
|
||||
// simple nsIInputStream implementation that wraps the push-back data
|
||||
// and returns NS_BASE_STREAM_WOULD_BLOCK when empty. unfortunately, none
|
||||
// of the string stream classes behave this way.
|
||||
//
|
||||
class nsInputStreamWrapper : public nsIInputStream
|
||||
{
|
||||
public:
|
||||
NS_DECL_ISUPPORTS
|
||||
NS_DECL_NSIINPUTSTREAM
|
||||
|
||||
nsInputStreamWrapper(const char *data, PRUint32 dataLen);
|
||||
virtual ~nsInputStreamWrapper();
|
||||
|
||||
private:
|
||||
const char *mData;
|
||||
PRUint32 mDataLen;
|
||||
PRUint32 mDataPos;
|
||||
};
|
||||
|
||||
//
|
||||
// helpers
|
||||
//
|
||||
PRBool IsDone_Locked();
|
||||
PRInt8 LocateTransaction_Locked(nsAHttpTransaction *);
|
||||
void DropTransaction_Locked(PRInt8);
|
||||
PRUint32 GetRequestSize_Locked();
|
||||
|
||||
private:
|
||||
enum {
|
||||
eTransactionReading = PR_BIT(1),
|
||||
eTransactionComplete = PR_BIT(2)
|
||||
};
|
||||
nsAHttpConnection *mConnection; // hard ref
|
||||
nsAHttpTransaction *mTransactionQ [NS_HTTP_MAX_PIPELINED_REQUESTS]; // hard refs
|
||||
PRUint32 mTransactionFlags[NS_HTTP_MAX_PIPELINED_REQUESTS];
|
||||
PRInt8 mNumTrans; // between 2 and NS_HTTP_MAX_PIPELINED_REQUESTS
|
||||
PRInt8 mCurrentReader; // index of transaction currently reading
|
||||
PRLock *mLock;
|
||||
nsresult mStatus;
|
||||
nsCOMPtr<nsIInputStream> mRequestData;
|
||||
};
|
||||
|
||||
#endif // nsHttpPipeline_h__
|
@ -200,6 +200,15 @@ nsHttpTransaction::TakeResponseHead()
|
||||
// nsHttpTransaction::nsAHttpTransaction
|
||||
//----------------------------------------------------------------------------
|
||||
|
||||
PRUint32
|
||||
nsHttpTransaction::GetRequestSize()
|
||||
{
|
||||
PRUint32 n = 0;
|
||||
if (mReqHeaderStream)
|
||||
mReqHeaderStream->Available(&n);
|
||||
return n;
|
||||
}
|
||||
|
||||
// called on the socket transport thread
|
||||
nsresult
|
||||
nsHttpTransaction::OnDataWritable(nsIOutputStream *os)
|
||||
@ -280,6 +289,7 @@ nsHttpTransaction::OnStopTransaction(nsresult status)
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
mTransactionDone = 1; // force this flag
|
||||
mStatus = status;
|
||||
|
||||
if (mListener) {
|
||||
@ -751,10 +761,6 @@ nsHttpTransaction::Cancel(nsresult status)
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
// the status must be set immediately as the cancelation may only take
|
||||
// action asynchronously.
|
||||
mStatus = status;
|
||||
|
||||
// if the transaction is already "done" then there is nothing more to do.
|
||||
// ie., our consumer _will_ eventually receive their OnStopRequest.
|
||||
PRInt32 priorVal = PR_AtomicSet(&mTransactionDone, 1);
|
||||
@ -763,6 +769,10 @@ nsHttpTransaction::Cancel(nsresult status)
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
// the status must be set immediately as the cancelation may only take
|
||||
// action asynchronously.
|
||||
mStatus = status;
|
||||
|
||||
return nsHttpHandler::get()->CancelTransaction(this, status);
|
||||
}
|
||||
|
||||
@ -884,8 +894,14 @@ nsHttpTransaction::Read(char *buf, PRUint32 count, PRUint32 *bytesWritten)
|
||||
|
||||
// even though count may be 0, we still want to call HandleContent
|
||||
// so it can complete the transaction if this is a "no-content" response.
|
||||
if (mHaveAllHeaders)
|
||||
return HandleContent(buf, count, bytesWritten);
|
||||
if (mHaveAllHeaders) {
|
||||
rv = HandleContent(buf, count, bytesWritten);
|
||||
if (NS_FAILED(rv)) return rv;
|
||||
// we may have read more than our share, in which case we must give
|
||||
// the excess bytes back to the connection
|
||||
if (mConnection && (count > *bytesWritten))
|
||||
mConnection->PushBack(buf + *bytesWritten, count - *bytesWritten);
|
||||
}
|
||||
|
||||
// wait for more data
|
||||
return NS_BASE_STREAM_WOULD_BLOCK;
|
||||
|
@ -85,6 +85,8 @@ public:
|
||||
void SetConnection(nsAHttpConnection *conn) { NS_IF_ADDREF(mConnection = conn); }
|
||||
void SetSecurityInfo(nsISupports *info) { mSecurityInfo = info; }
|
||||
void GetNotificationCallbacks(nsIInterfaceRequestor **cb) { NS_IF_ADDREF(*cb = mCallbacks); }
|
||||
PRUint32 GetRequestSize();
|
||||
PRUint32 GetContentRead() { return mContentRead; }
|
||||
nsresult OnDataWritable(nsIOutputStream *);
|
||||
nsresult OnDataReadable(nsIInputStream *);
|
||||
nsresult OnStopTransaction(nsresult);
|
||||
|
Loading…
Reference in New Issue
Block a user