Back to index

lightning-sunbird  0.9+nobinonly
nsHttpPipeline.cpp
Go to the documentation of this file.
00001 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
00002 /* ***** BEGIN LICENSE BLOCK *****
00003  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
00004  *
00005  * The contents of this file are subject to the Mozilla Public License Version
00006  * 1.1 (the "License"); you may not use this file except in compliance with
00007  * the License. You may obtain a copy of the License at
00008  * http://www.mozilla.org/MPL/
00009  *
00010  * Software distributed under the License is distributed on an "AS IS" basis,
00011  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
00012  * for the specific language governing rights and limitations under the
00013  * License.
00014  *
00015  * The Original Code is Mozilla.
00016  *
00017  * The Initial Developer of the Original Code is
00018  * Netscape Communications.
00019  * Portions created by the Initial Developer are Copyright (C) 2001
00020  * the Initial Developer. All Rights Reserved.
00021  *
00022  * Contributor(s):
00023  *   Darin Fisher <darin@netscape.com> (original author)
00024  *
00025  * Alternatively, the contents of this file may be used under the terms of
00026  * either the GNU General Public License Version 2 or later (the "GPL"), or
00027  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
00028  * in which case the provisions of the GPL or the LGPL are applicable instead
00029  * of those above. If you wish to allow use of your version of this file only
00030  * under the terms of either the GPL or the LGPL, and not to allow others to
00031  * use your version of this file under the terms of the MPL, indicate your
00032  * decision by deleting the provisions above and replace them with the notice
00033  * and other provisions required by the GPL or the LGPL. If you do not delete
00034  * the provisions above, a recipient may use your version of this file under
00035  * the terms of any one of the MPL, the GPL or the LGPL.
00036  *
00037  * ***** END LICENSE BLOCK ***** */
00038 
00039 #include <stdlib.h>
00040 #include "nsHttp.h"
00041 #include "nsHttpPipeline.h"
00042 #include "nsHttpHandler.h"
00043 #include "nsIOService.h"
00044 #include "nsIRequest.h"
00045 #include "nsISocketTransport.h"
00046 #include "nsIStringStream.h"
00047 #include "nsIPipe.h"
00048 #include "nsCOMPtr.h"
00049 #include "nsComponentManagerUtils.h"
00050 #include "nsAutoLock.h"
00051 
00052 #ifdef DEBUG
00053 #include "prthread.h"
00054 // defined by the socket transport service while active
00055 extern PRThread *gSocketThread;
00056 #endif
00057 
00058 //-----------------------------------------------------------------------------
00059 // nsHttpPushBackWriter
00060 //-----------------------------------------------------------------------------
00061 
00062 class nsHttpPushBackWriter : public nsAHttpSegmentWriter
00063 {
00064 public:
00065     nsHttpPushBackWriter(const char *buf, PRUint32 bufLen)
00066         : mBuf(buf)
00067         , mBufLen(bufLen)
00068         { }
00069     virtual ~nsHttpPushBackWriter() {}
00070 
00071     nsresult OnWriteSegment(char *buf, PRUint32 count, PRUint32 *countWritten)
00072     {
00073         if (mBufLen == 0)
00074             return NS_BASE_STREAM_CLOSED;
00075 
00076         if (count > mBufLen)
00077             count = mBufLen;
00078 
00079         memcpy(buf, mBuf, count);
00080 
00081         mBuf += count;
00082         mBufLen -= count;
00083         *countWritten = count;
00084         return NS_OK;
00085     }
00086 
00087 private:
00088     const char *mBuf;
00089     PRUint32    mBufLen;
00090 };
00091 
00092 //-----------------------------------------------------------------------------
00093 // nsHttpPipeline <public>
00094 //-----------------------------------------------------------------------------
00095 
00096 nsHttpPipeline::nsHttpPipeline()
00097     : mConnection(nsnull)
00098     , mStatus(NS_OK)
00099     , mRequestIsPartial(PR_FALSE)
00100     , mResponseIsPartial(PR_FALSE)
00101     , mClosed(PR_FALSE)
00102     , mPushBackBuf(nsnull)
00103     , mPushBackLen(0)
00104     , mPushBackMax(0)
00105 {
00106 }
00107 
00108 nsHttpPipeline::~nsHttpPipeline()
00109 {
00110     // make sure we aren't still holding onto any transactions!
00111     Close(NS_ERROR_ABORT);
00112 
00113     if (mPushBackBuf)
00114         free(mPushBackBuf);
00115 }
00116 
00117 nsresult
00118 nsHttpPipeline::AddTransaction(nsAHttpTransaction *trans)
00119 {
00120     LOG(("nsHttpPipeline::AddTransaction [this=%x trans=%x]\n", this, trans));
00121 
00122     NS_ADDREF(trans);
00123     mRequestQ.AppendElement(trans);
00124 
00125     if (mConnection) {
00126         trans->SetConnection(this);
00127 
00128         if (mRequestQ.Count() == 1)
00129             mConnection->ResumeSend();
00130     }
00131 
00132     return NS_OK;
00133 }
00134 
00135 //-----------------------------------------------------------------------------
00136 // nsHttpPipeline::nsISupports
00137 //-----------------------------------------------------------------------------
00138 
00139 NS_IMPL_THREADSAFE_ADDREF(nsHttpPipeline)
00140 NS_IMPL_THREADSAFE_RELEASE(nsHttpPipeline)
00141 
00142 // multiple inheritance fun :-)
00143 NS_INTERFACE_MAP_BEGIN(nsHttpPipeline)
00144     NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsAHttpConnection)
00145 NS_INTERFACE_MAP_END
00146 
00147 
00148 //-----------------------------------------------------------------------------
00149 // nsHttpPipeline::nsAHttpConnection
00150 //-----------------------------------------------------------------------------
00151 
00152 nsresult
00153 nsHttpPipeline::OnHeadersAvailable(nsAHttpTransaction *trans,
00154                                    nsHttpRequestHead *requestHead,
00155                                    nsHttpResponseHead *responseHead,
00156                                    PRBool *reset)
00157 {
00158     LOG(("nsHttpPipeline::OnHeadersAvailable [this=%x]\n", this));
00159 
00160     NS_ASSERTION(PR_GetCurrentThread() == gSocketThread, "wrong thread");
00161     NS_ASSERTION(mConnection, "no connection");
00162 
00163     // trans has now received its response headers; forward to the real connection
00164     return mConnection->OnHeadersAvailable(trans, requestHead, responseHead, reset);
00165 }
00166 
00167 nsresult
00168 nsHttpPipeline::ResumeSend()
00169 {
00170     NS_NOTREACHED("nsHttpPipeline::ResumeSend");
00171     return NS_ERROR_UNEXPECTED;
00172 }
00173 
00174 nsresult
00175 nsHttpPipeline::ResumeRecv()
00176 {
00177     NS_ASSERTION(PR_GetCurrentThread() == gSocketThread, "wrong thread");
00178     NS_ASSERTION(mConnection, "no connection");
00179     return mConnection->ResumeRecv();
00180 }
00181 
00182 void
00183 nsHttpPipeline::CloseTransaction(nsAHttpTransaction *trans, nsresult reason)
00184 {
00185     LOG(("nsHttpPipeline::CloseTransaction [this=%x trans=%x reason=%x]\n",
00186         this, trans, reason));
00187 
00188     NS_ASSERTION(PR_GetCurrentThread() == gSocketThread, "wrong thread");
00189     NS_ASSERTION(NS_FAILED(reason), "expecting failure code");
00190 
00191     // the specified transaction is to be closed with the given "reason"
00192     
00193     PRInt32 index;
00194     PRBool killPipeline = PR_FALSE;
00195 
00196     index = mRequestQ.IndexOf(trans);
00197     if (index >= 0) {
00198         if (index == 0 && mRequestIsPartial) {
00199             // the transaction is in the request queue.  check to see if any of
00200             // its data has been written out yet.
00201             killPipeline = PR_TRUE;
00202         }
00203         mRequestQ.RemoveElementAt(index);
00204     }
00205     else {
00206         index = mResponseQ.IndexOf(trans);
00207         if (index >= 0)
00208             mResponseQ.RemoveElementAt(index);
00209         // while we could avoid killing the pipeline if this transaction is the
00210         // last transaction in the pipeline, there doesn't seem to be that much
00211         // value in doing so.  most likely if this transaction is going away,
00212         // the others will be shortly as well.
00213         killPipeline = PR_TRUE;
00214     }
00215 
00216     trans->Close(reason);
00217     NS_RELEASE(trans);
00218 
00219     if (killPipeline) {
00220         if (mConnection)
00221             mConnection->CloseTransaction(this, reason);
00222         else
00223             Close(reason);
00224     }
00225 }
00226 
00227 void
00228 nsHttpPipeline::GetConnectionInfo(nsHttpConnectionInfo **result)
00229 {
00230     NS_ASSERTION(mConnection, "no connection");
00231     mConnection->GetConnectionInfo(result);
00232 }
00233 
00234 void
00235 nsHttpPipeline::GetSecurityInfo(nsISupports **result)
00236 {
00237     NS_ASSERTION(mConnection, "no connection");
00238     mConnection->GetSecurityInfo(result);
00239 }
00240 
00241 PRBool
00242 nsHttpPipeline::IsPersistent()
00243 {
00244     return PR_TRUE; // pipelining requires this
00245 }
00246 
00247 PRBool
00248 nsHttpPipeline::IsReused()
00249 {
00250     return PR_TRUE; // pipelining requires this
00251 }
00252 
00253 nsresult
00254 nsHttpPipeline::PushBack(const char *data, PRUint32 length)
00255 {
00256     LOG(("nsHttpPipeline::PushBack [this=%x len=%u]\n", this, length));
00257     
00258     NS_ASSERTION(PR_GetCurrentThread() == gSocketThread, "wrong thread");
00259     NS_ASSERTION(mPushBackLen == 0, "push back buffer already has data!");
00260 
00261     // PushBack is called recursively from WriteSegments
00262 
00263     // XXX we have a design decision to make here.  either we buffer the data
00264     // and process it when we return to WriteSegments, or we attempt to move
00265     // onto the next transaction from here.  doing so adds complexity with the
00266     // benefit of eliminating the extra buffer copy.  the buffer is at most
00267     // 4096 bytes, so it is really unclear if there is any value in the added
00268     // complexity.  besides simplicity, buffering this data has the advantage
00269     // that we'll call close on the transaction sooner, which will wake up
00270     // the HTTP channel sooner to continue with its work.
00271 
00272     if (!mPushBackBuf) {
00273         mPushBackMax = length;
00274         mPushBackBuf = (char *) malloc(mPushBackMax);
00275         if (!mPushBackBuf)
00276             return NS_ERROR_OUT_OF_MEMORY;
00277     }
00278     else if (length > mPushBackMax) {
00279         // grow push back buffer as necessary.
00280         NS_ASSERTION(length <= NS_HTTP_SEGMENT_SIZE, "too big");
00281         mPushBackMax = length;
00282         mPushBackBuf = (char *) realloc(mPushBackBuf, mPushBackMax);
00283         if (!mPushBackBuf)
00284             return NS_ERROR_OUT_OF_MEMORY;
00285     }
00286  
00287     memcpy(mPushBackBuf, data, length);
00288     mPushBackLen = length;
00289 
00290     return NS_OK;
00291 }
00292 
00293 //-----------------------------------------------------------------------------
00294 // nsHttpPipeline::nsAHttpConnection
00295 //-----------------------------------------------------------------------------
00296 
00297 void
00298 nsHttpPipeline::SetConnection(nsAHttpConnection *conn)
00299 {
00300     LOG(("nsHttpPipeline::SetConnection [this=%x conn=%x]\n", this, conn));
00301 
00302     NS_ASSERTION(PR_GetCurrentThread() == gSocketThread, "wrong thread");
00303     NS_ASSERTION(!mConnection, "already have a connection");
00304 
00305     NS_IF_ADDREF(mConnection = conn);
00306 
00307     PRInt32 i, count = mRequestQ.Count();
00308     for (i=0; i<count; ++i)
00309         Request(i)->SetConnection(this);
00310 }
00311 
00312 void
00313 nsHttpPipeline::GetSecurityCallbacks(nsIInterfaceRequestor **result)
00314 {
00315     NS_ASSERTION(PR_GetCurrentThread() == gSocketThread, "wrong thread");
00316 
00317     // return security callbacks from first request
00318     nsAHttpTransaction *trans = Request(0);
00319     if (trans)
00320         trans->GetSecurityCallbacks(result);
00321     else
00322         *result = nsnull;
00323 }
00324 
00325 void
00326 nsHttpPipeline::OnTransportStatus(nsresult status, PRUint64 progress)
00327 {
00328     LOG(("nsHttpPipeline::OnStatus [this=%x status=%x progress=%llu]\n",
00329         this, status, progress));
00330 
00331     NS_ASSERTION(PR_GetCurrentThread() == gSocketThread, "wrong thread");
00332 
00333     nsAHttpTransaction *trans;
00334     switch (status) {
00335     case NS_NET_STATUS_RECEIVING_FROM:
00336         // forward this only to the transaction currently recieving data 
00337         trans = Response(0);
00338         if (trans)
00339             trans->OnTransportStatus(status, progress);
00340         break;
00341     default:
00342         // forward other notifications to all transactions
00343         PRInt32 i, count = mRequestQ.Count();
00344         for (i=0; i<count; ++i) {
00345             trans = Request(i);
00346             if (trans)
00347                 trans->OnTransportStatus(status, progress);
00348         }
00349         break;
00350     }
00351 }
00352 
00353 PRBool
00354 nsHttpPipeline::IsDone()
00355 {
00356     return (mRequestQ.Count() == 0) && (mResponseQ.Count() == 0);
00357 }
00358 
00359 nsresult
00360 nsHttpPipeline::Status()
00361 {
00362     return mStatus;
00363 }
00364 
00365 PRUint32
00366 nsHttpPipeline::Available()
00367 {
00368     PRUint32 result = 0;
00369 
00370     PRInt32 i, count = mRequestQ.Count();
00371     for (i=0; i<count; ++i)
00372         result += Request(i)->Available();
00373     return result;
00374 }
00375 
00376 NS_METHOD
00377 nsHttpPipeline::ReadFromPipe(nsIInputStream *stream,
00378                              void *closure,
00379                              const char *buf,
00380                              PRUint32 offset,
00381                              PRUint32 count,
00382                              PRUint32 *countRead)
00383 {
00384     nsHttpPipeline *self = (nsHttpPipeline *) closure;
00385     return self->mReader->OnReadSegment(buf, count, countRead);
00386 }
00387 
00388 nsresult
00389 nsHttpPipeline::ReadSegments(nsAHttpSegmentReader *reader,
00390                              PRUint32 count,
00391                              PRUint32 *countRead)
00392 {
00393     LOG(("nsHttpPipeline::ReadSegments [this=%x count=%u]\n", this, count));
00394 
00395     NS_ASSERTION(PR_GetCurrentThread() == gSocketThread, "wrong thread");
00396 
00397     if (mClosed) {
00398         *countRead = 0;
00399         return mStatus;
00400     }
00401 
00402     nsresult rv;
00403     PRUint32 avail = 0;
00404     if (mSendBufIn) {
00405         rv = mSendBufIn->Available(&avail);
00406         if (NS_FAILED(rv)) return rv;
00407     }
00408 
00409     if (avail == 0) {
00410         rv = FillSendBuf();
00411         if (NS_FAILED(rv)) return rv;
00412 
00413         rv = mSendBufIn->Available(&avail);
00414         if (NS_FAILED(rv)) return rv;
00415 
00416         // return EOF if send buffer is empty
00417         if (avail == 0) {
00418             *countRead = 0;
00419             return NS_OK;
00420         }
00421     }
00422 
00423     // read no more than what was requested
00424     if (avail > count)
00425         avail = count;
00426 
00427     mReader = reader;
00428 
00429     rv = mSendBufIn->ReadSegments(ReadFromPipe, this, avail, countRead);
00430 
00431     mReader = nsnull;
00432     return rv;
00433 }
00434 
00435 nsresult
00436 nsHttpPipeline::WriteSegments(nsAHttpSegmentWriter *writer,
00437                               PRUint32 count,
00438                               PRUint32 *countWritten)
00439 {
00440     LOG(("nsHttpPipeline::WriteSegments [this=%x count=%u]\n", this, count));
00441 
00442     NS_ASSERTION(PR_GetCurrentThread() == gSocketThread, "wrong thread");
00443 
00444     if (mClosed)
00445         return NS_SUCCEEDED(mStatus) ? NS_BASE_STREAM_CLOSED : mStatus;
00446 
00447     nsAHttpTransaction *trans; 
00448     nsresult rv;
00449 
00450     trans = Response(0);
00451     if (!trans) {
00452         if (mRequestQ.Count() > 0)
00453             rv = NS_BASE_STREAM_WOULD_BLOCK;
00454         else
00455             rv = NS_BASE_STREAM_CLOSED;
00456     }
00457     else {
00458         // 
00459         // ask the transaction to consume data from the connection.
00460         // PushBack may be called recursively.
00461         //
00462         rv = trans->WriteSegments(writer, count, countWritten);
00463 
00464         if (rv == NS_BASE_STREAM_CLOSED || trans->IsDone()) {
00465             trans->Close(NS_OK);
00466             NS_RELEASE(trans);
00467             mResponseQ.RemoveElementAt(0);
00468             mResponseIsPartial = PR_FALSE;
00469 
00470             // ask the connection manager to add additional transactions
00471             // to our pipeline.
00472             gHttpHandler->ConnMgr()->AddTransactionToPipeline(this);
00473         }
00474         else
00475             mResponseIsPartial = PR_TRUE;
00476     }
00477 
00478     if (mPushBackLen) {
00479         nsHttpPushBackWriter writer(mPushBackBuf, mPushBackLen);
00480         PRUint32 len = mPushBackLen, n;
00481         mPushBackLen = 0;
00482         // the push back buffer is never larger than NS_HTTP_SEGMENT_SIZE,
00483         // so we are guaranteed that the next response will eat the entire
00484         // push back buffer (even though it might again call PushBack).
00485         rv = WriteSegments(&writer, len, &n);
00486     }
00487 
00488     return rv;
00489 }
00490 
00491 void
00492 nsHttpPipeline::Close(nsresult reason)
00493 {
00494     LOG(("nsHttpPipeline::Close [this=%x reason=%x]\n", this, reason));
00495 
00496     if (mClosed) {
00497         LOG(("  already closed\n"));
00498         return;
00499     }
00500 
00501     // the connection is going away!
00502     mStatus = reason;
00503     mClosed = PR_TRUE;
00504 
00505     // we must no longer reference the connection!
00506     NS_IF_RELEASE(mConnection);
00507 
00508     PRUint32 i, count;
00509     nsAHttpTransaction *trans;
00510 
00511     // any pending requests can ignore this error and be restarted
00512     count = mRequestQ.Count();
00513     for (i=0; i<count; ++i) {
00514         trans = Request(i);
00515         trans->Close(NS_ERROR_NET_RESET);
00516         NS_RELEASE(trans);
00517     }
00518     mRequestQ.Clear();
00519 
00520     trans = Response(0);
00521     if (trans) {
00522         // if the current response is partially complete, then it cannot be
00523         // restarted and will have to fail with the status of the connection.
00524         if (mResponseIsPartial)
00525             trans->Close(reason);
00526         else
00527             trans->Close(NS_ERROR_NET_RESET);
00528         NS_RELEASE(trans);
00529         
00530         // any remaining pending responses can be restarted
00531         count = mResponseQ.Count();
00532         for (i=1; i<count; ++i) {
00533             trans = Response(i);
00534             trans->Close(NS_ERROR_NET_RESET);
00535             NS_RELEASE(trans);
00536         }
00537         mResponseQ.Clear();
00538     }
00539 }
00540 
00541 nsresult
00542 nsHttpPipeline::OnReadSegment(const char *segment,
00543                               PRUint32 count,
00544                               PRUint32 *countRead)
00545 {
00546     return mSendBufOut->Write(segment, count, countRead);
00547 }
00548 
00549 nsresult
00550 nsHttpPipeline::FillSendBuf()
00551 {
00552     // reads from request queue, moving transactions to response queue
00553     // when they have been completely read.
00554 
00555     nsresult rv;
00556     
00557     if (!mSendBufIn) {
00558         // allocate a single-segment pipe
00559         rv = NS_NewPipe(getter_AddRefs(mSendBufIn),
00560                         getter_AddRefs(mSendBufOut),
00561                         NS_HTTP_SEGMENT_SIZE,
00562                         NS_HTTP_SEGMENT_SIZE,
00563                         PR_TRUE, PR_TRUE,
00564                         nsIOService::gBufferCache);
00565         if (NS_FAILED(rv)) return rv;
00566     }
00567 
00568     PRUint32 n, avail;
00569     nsAHttpTransaction *trans;
00570     while ((trans = Request(0)) != nsnull) {
00571         avail = trans->Available();
00572         if (avail) {
00573             rv = trans->ReadSegments(this, avail, &n);
00574             if (NS_FAILED(rv)) return rv;
00575             
00576             if (n == 0) {
00577                 LOG(("send pipe is full"));
00578                 break;
00579             }
00580         }
00581         avail = trans->Available();
00582         if (avail == 0) {
00583             // move transaction from request queue to response queue
00584             mRequestQ.RemoveElementAt(0);
00585             mResponseQ.AppendElement(trans);
00586             mRequestIsPartial = PR_FALSE;
00587         }
00588         else
00589             mRequestIsPartial = PR_TRUE;
00590     }
00591     return NS_OK;
00592 }