1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /* ***** BEGIN LICENSE BLOCK *****
3 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
5 * The contents of this file are subject to the Mozilla Public License Version
6 * 1.1 (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 * http://www.mozilla.org/MPL/
10 * Software distributed under the License is distributed on an "AS IS" basis,
11 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12 * for the specific language governing rights and limitations under the
15 * The Original Code is Mozilla.
17 * The Initial Developer of the Original Code is
18 * Netscape Communications.
19 * Portions created by the Initial Developer are Copyright (C) 2001
20 * the Initial Developer. All Rights Reserved.
23 * Darin Fisher <darin@netscape.com> (original author)
25 * Alternatively, the contents of this file may be used under the terms of
26 * either the GNU General Public License Version 2 or later (the "GPL"), or
27 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
28 * in which case the provisions of the GPL or the LGPL are applicable instead
29 * of those above. If you wish to allow use of your version of this file only
30 * under the terms of either the GPL or the LGPL, and not to allow others to
31 * use your version of this file under the terms of the MPL, indicate your
32 * decision by deleting the provisions above and replace them with the notice
33 * and other provisions required by the GPL or the LGPL. If you do not delete
34 * the provisions above, a recipient may use your version of this file under
35 * the terms of any one of the MPL, the GPL or the LGPL.
37 * ***** END LICENSE BLOCK ***** */
41 #include "nsHttpPipeline.h"
42 #include "nsHttpHandler.h"
43 #include "nsIOService.h"
44 #include "nsIRequest.h"
45 #include "nsISocketTransport.h"
46 #include "nsIStringStream.h"
49 #include "nsComponentManagerUtils.h"
50 #include "nsAutoLock.h"
54 // defined by the socket transport service while active
55 extern PRThread
*gSocketThread
;
58 //-----------------------------------------------------------------------------
59 // nsHttpPushBackWriter
60 //-----------------------------------------------------------------------------
62 class nsHttpPushBackWriter
: public nsAHttpSegmentWriter
65 nsHttpPushBackWriter(const char *buf
, PRUint32 bufLen
)
69 virtual ~nsHttpPushBackWriter() {}
71 nsresult
OnWriteSegment(char *buf
, PRUint32 count
, PRUint32
*countWritten
)
74 return NS_BASE_STREAM_CLOSED
;
79 memcpy(buf
, mBuf
, count
);
83 *countWritten
= count
;
92 //-----------------------------------------------------------------------------
93 // nsHttpPipeline <public>
94 //-----------------------------------------------------------------------------
96 nsHttpPipeline::nsHttpPipeline()
99 , mRequestIsPartial(PR_FALSE
)
100 , mResponseIsPartial(PR_FALSE
)
102 , mPushBackBuf(nsnull
)
108 nsHttpPipeline::~nsHttpPipeline()
110 // make sure we aren't still holding onto any transactions!
111 Close(NS_ERROR_ABORT
);
118 nsHttpPipeline::AddTransaction(nsAHttpTransaction
*trans
)
120 LOG(("nsHttpPipeline::AddTransaction [this=%x trans=%x]\n", this, trans
));
123 mRequestQ
.AppendElement(trans
);
126 trans
->SetConnection(this);
128 if (mRequestQ
.Count() == 1)
129 mConnection
->ResumeSend();
135 //-----------------------------------------------------------------------------
136 // nsHttpPipeline::nsISupports
137 //-----------------------------------------------------------------------------
139 NS_IMPL_THREADSAFE_ADDREF(nsHttpPipeline
)
140 NS_IMPL_THREADSAFE_RELEASE(nsHttpPipeline
)
142 // multiple inheritance fun :-)
143 NS_INTERFACE_MAP_BEGIN(nsHttpPipeline
)
144 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports
, nsAHttpConnection
)
148 //-----------------------------------------------------------------------------
149 // nsHttpPipeline::nsAHttpConnection
150 //-----------------------------------------------------------------------------
153 nsHttpPipeline::OnHeadersAvailable(nsAHttpTransaction
*trans
,
154 nsHttpRequestHead
*requestHead
,
155 nsHttpResponseHead
*responseHead
,
158 LOG(("nsHttpPipeline::OnHeadersAvailable [this=%x]\n", this));
160 NS_ASSERTION(PR_GetCurrentThread() == gSocketThread
, "wrong thread");
161 NS_ASSERTION(mConnection
, "no connection");
163 // trans has now received its response headers; forward to the real connection
164 return mConnection
->OnHeadersAvailable(trans
, requestHead
, responseHead
, reset
);
168 nsHttpPipeline::ResumeSend()
170 NS_NOTREACHED("nsHttpPipeline::ResumeSend");
171 return NS_ERROR_UNEXPECTED
;
175 nsHttpPipeline::ResumeRecv()
177 NS_ASSERTION(PR_GetCurrentThread() == gSocketThread
, "wrong thread");
178 NS_ASSERTION(mConnection
, "no connection");
179 return mConnection
->ResumeRecv();
183 nsHttpPipeline::CloseTransaction(nsAHttpTransaction
*trans
, nsresult reason
)
185 LOG(("nsHttpPipeline::CloseTransaction [this=%x trans=%x reason=%x]\n",
186 this, trans
, reason
));
188 NS_ASSERTION(PR_GetCurrentThread() == gSocketThread
, "wrong thread");
189 NS_ASSERTION(NS_FAILED(reason
), "expecting failure code");
191 // the specified transaction is to be closed with the given "reason"
194 PRBool killPipeline
= PR_FALSE
;
196 index
= mRequestQ
.IndexOf(trans
);
198 if (index
== 0 && mRequestIsPartial
) {
199 // the transaction is in the request queue. check to see if any of
200 // its data has been written out yet.
201 killPipeline
= PR_TRUE
;
203 mRequestQ
.RemoveElementAt(index
);
206 index
= mResponseQ
.IndexOf(trans
);
208 mResponseQ
.RemoveElementAt(index
);
209 // while we could avoid killing the pipeline if this transaction is the
210 // last transaction in the pipeline, there doesn't seem to be that much
211 // value in doing so. most likely if this transaction is going away,
212 // the others will be shortly as well.
213 killPipeline
= PR_TRUE
;
216 trans
->Close(reason
);
221 mConnection
->CloseTransaction(this, reason
);
228 nsHttpPipeline::GetConnectionInfo(nsHttpConnectionInfo
**result
)
230 NS_ASSERTION(mConnection
, "no connection");
231 mConnection
->GetConnectionInfo(result
);
235 nsHttpPipeline::GetSecurityInfo(nsISupports
**result
)
237 NS_ASSERTION(mConnection
, "no connection");
238 mConnection
->GetSecurityInfo(result
);
242 nsHttpPipeline::IsPersistent()
244 return PR_TRUE
; // pipelining requires this
248 nsHttpPipeline::IsReused()
250 return PR_TRUE
; // pipelining requires this
254 nsHttpPipeline::PushBack(const char *data
, PRUint32 length
)
256 LOG(("nsHttpPipeline::PushBack [this=%x len=%u]\n", this, length
));
258 NS_ASSERTION(PR_GetCurrentThread() == gSocketThread
, "wrong thread");
259 NS_ASSERTION(mPushBackLen
== 0, "push back buffer already has data!");
261 // PushBack is called recursively from WriteSegments
263 // XXX we have a design decision to make here. either we buffer the data
264 // and process it when we return to WriteSegments, or we attempt to move
265 // onto the next transaction from here. doing so adds complexity with the
266 // benefit of eliminating the extra buffer copy. the buffer is at most
267 // 4096 bytes, so it is really unclear if there is any value in the added
268 // complexity. besides simplicity, buffering this data has the advantage
269 // that we'll call close on the transaction sooner, which will wake up
270 // the HTTP channel sooner to continue with its work.
273 mPushBackMax
= length
;
274 mPushBackBuf
= (char *) malloc(mPushBackMax
);
276 return NS_ERROR_OUT_OF_MEMORY
;
278 else if (length
> mPushBackMax
) {
279 // grow push back buffer as necessary.
280 NS_ASSERTION(length
<= NS_HTTP_SEGMENT_SIZE
, "too big");
281 mPushBackMax
= length
;
282 mPushBackBuf
= (char *) realloc(mPushBackBuf
, mPushBackMax
);
284 return NS_ERROR_OUT_OF_MEMORY
;
287 memcpy(mPushBackBuf
, data
, length
);
288 mPushBackLen
= length
;
293 //-----------------------------------------------------------------------------
294 // nsHttpPipeline::nsAHttpConnection
295 //-----------------------------------------------------------------------------
298 nsHttpPipeline::SetConnection(nsAHttpConnection
*conn
)
300 LOG(("nsHttpPipeline::SetConnection [this=%x conn=%x]\n", this, conn
));
302 NS_ASSERTION(PR_GetCurrentThread() == gSocketThread
, "wrong thread");
303 NS_ASSERTION(!mConnection
, "already have a connection");
305 NS_IF_ADDREF(mConnection
= conn
);
307 PRInt32 i
, count
= mRequestQ
.Count();
308 for (i
=0; i
<count
; ++i
)
309 Request(i
)->SetConnection(this);
313 nsHttpPipeline::GetSecurityCallbacks(nsIInterfaceRequestor
**result
)
315 NS_ASSERTION(PR_GetCurrentThread() == gSocketThread
, "wrong thread");
317 // return security callbacks from first request
318 nsAHttpTransaction
*trans
= Request(0);
320 trans
->GetSecurityCallbacks(result
);
326 nsHttpPipeline::OnTransportStatus(nsresult status
, PRUint64 progress
)
328 LOG(("nsHttpPipeline::OnStatus [this=%x status=%x progress=%llu]\n",
329 this, status
, progress
));
331 NS_ASSERTION(PR_GetCurrentThread() == gSocketThread
, "wrong thread");
333 nsAHttpTransaction
*trans
;
335 case NS_NET_STATUS_RECEIVING_FROM
:
336 // forward this only to the transaction currently recieving data
339 trans
->OnTransportStatus(status
, progress
);
342 // forward other notifications to all transactions
343 PRInt32 i
, count
= mRequestQ
.Count();
344 for (i
=0; i
<count
; ++i
) {
347 trans
->OnTransportStatus(status
, progress
);
354 nsHttpPipeline::IsDone()
356 return (mRequestQ
.Count() == 0) && (mResponseQ
.Count() == 0);
360 nsHttpPipeline::Status()
366 nsHttpPipeline::Available()
370 PRInt32 i
, count
= mRequestQ
.Count();
371 for (i
=0; i
<count
; ++i
)
372 result
+= Request(i
)->Available();
377 nsHttpPipeline::ReadFromPipe(nsIInputStream
*stream
,
384 nsHttpPipeline
*self
= (nsHttpPipeline
*) closure
;
385 return self
->mReader
->OnReadSegment(buf
, count
, countRead
);
389 nsHttpPipeline::ReadSegments(nsAHttpSegmentReader
*reader
,
393 LOG(("nsHttpPipeline::ReadSegments [this=%x count=%u]\n", this, count
));
395 NS_ASSERTION(PR_GetCurrentThread() == gSocketThread
, "wrong thread");
405 rv
= mSendBufIn
->Available(&avail
);
406 if (NS_FAILED(rv
)) return rv
;
411 if (NS_FAILED(rv
)) return rv
;
413 rv
= mSendBufIn
->Available(&avail
);
414 if (NS_FAILED(rv
)) return rv
;
416 // return EOF if send buffer is empty
423 // read no more than what was requested
429 rv
= mSendBufIn
->ReadSegments(ReadFromPipe
, this, avail
, countRead
);
436 nsHttpPipeline::WriteSegments(nsAHttpSegmentWriter
*writer
,
438 PRUint32
*countWritten
)
440 LOG(("nsHttpPipeline::WriteSegments [this=%x count=%u]\n", this, count
));
442 NS_ASSERTION(PR_GetCurrentThread() == gSocketThread
, "wrong thread");
445 return NS_SUCCEEDED(mStatus
) ? NS_BASE_STREAM_CLOSED
: mStatus
;
447 nsAHttpTransaction
*trans
;
452 if (mRequestQ
.Count() > 0)
453 rv
= NS_BASE_STREAM_WOULD_BLOCK
;
455 rv
= NS_BASE_STREAM_CLOSED
;
459 // ask the transaction to consume data from the connection.
460 // PushBack may be called recursively.
462 rv
= trans
->WriteSegments(writer
, count
, countWritten
);
464 if (rv
== NS_BASE_STREAM_CLOSED
|| trans
->IsDone()) {
467 mResponseQ
.RemoveElementAt(0);
468 mResponseIsPartial
= PR_FALSE
;
470 // ask the connection manager to add additional transactions
472 gHttpHandler
->ConnMgr()->AddTransactionToPipeline(this);
475 mResponseIsPartial
= PR_TRUE
;
479 nsHttpPushBackWriter
writer(mPushBackBuf
, mPushBackLen
);
480 PRUint32 len
= mPushBackLen
, n
;
482 // the push back buffer is never larger than NS_HTTP_SEGMENT_SIZE,
483 // so we are guaranteed that the next response will eat the entire
484 // push back buffer (even though it might again call PushBack).
485 rv
= WriteSegments(&writer
, len
, &n
);
492 nsHttpPipeline::Close(nsresult reason
)
494 LOG(("nsHttpPipeline::Close [this=%x reason=%x]\n", this, reason
));
497 LOG((" already closed\n"));
501 // the connection is going away!
505 // we must no longer reference the connection!
506 NS_IF_RELEASE(mConnection
);
509 nsAHttpTransaction
*trans
;
511 // any pending requests can ignore this error and be restarted
512 count
= mRequestQ
.Count();
513 for (i
=0; i
<count
; ++i
) {
515 trans
->Close(NS_ERROR_NET_RESET
);
522 // if the current response is partially complete, then it cannot be
523 // restarted and will have to fail with the status of the connection.
524 if (mResponseIsPartial
)
525 trans
->Close(reason
);
527 trans
->Close(NS_ERROR_NET_RESET
);
530 // any remaining pending responses can be restarted
531 count
= mResponseQ
.Count();
532 for (i
=1; i
<count
; ++i
) {
534 trans
->Close(NS_ERROR_NET_RESET
);
542 nsHttpPipeline::OnReadSegment(const char *segment
,
546 return mSendBufOut
->Write(segment
, count
, countRead
);
550 nsHttpPipeline::FillSendBuf()
552 // reads from request queue, moving transactions to response queue
553 // when they have been completely read.
558 // allocate a single-segment pipe
559 rv
= NS_NewPipe(getter_AddRefs(mSendBufIn
),
560 getter_AddRefs(mSendBufOut
),
561 NS_HTTP_SEGMENT_SIZE
,
562 NS_HTTP_SEGMENT_SIZE
,
564 nsIOService::gBufferCache
);
565 if (NS_FAILED(rv
)) return rv
;
569 nsAHttpTransaction
*trans
;
570 while ((trans
= Request(0)) != nsnull
) {
571 avail
= trans
->Available();
573 rv
= trans
->ReadSegments(this, avail
, &n
);
574 if (NS_FAILED(rv
)) return rv
;
577 LOG(("send pipe is full"));
581 avail
= trans
->Available();
583 // move transaction from request queue to response queue
584 mRequestQ
.RemoveElementAt(0);
585 mResponseQ
.AppendElement(trans
);
586 mRequestIsPartial
= PR_FALSE
;
589 mRequestIsPartial
= PR_TRUE
;