implement HTTP/1.1 pipelining, b=93054

r=gagan, sr=rpotts
This commit is contained in:
darin%netscape.com 2002-04-19 22:25:23 +00:00
parent d1eef69879
commit b4db8085e5
16 changed files with 1099 additions and 32 deletions

View File

@ -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

View File

@ -51,6 +51,7 @@ CPPSRCS = \
nsHttpAuthCache.cpp \
nsHttpBasicAuth.cpp \
nsHttpDigestAuth.cpp \
nsHttpPipeline.cpp \
$(NULL)
LOCAL_INCLUDES=-I$(srcdir)/../../../streamconv/converters

View File

@ -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 \

View File

@ -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__

View File

@ -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).

View File

@ -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...
//-----------------------------------------------------------------------------

View File

@ -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);

View File

@ -185,6 +185,7 @@ private:
PRPackedBool mIsPending;
PRPackedBool mApplyConversion;
PRPackedBool mAllowPipelining;
PRPackedBool mFromCacheOnly;
PRPackedBool mCachedContentIsValid;
PRPackedBool mCachedContentIsPartial;

View File

@ -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

View File

@ -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;

View File

@ -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();
}

View File

@ -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;

View 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;
}

View 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__

View File

@ -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;

View File

@ -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);