Bug 458256. Use LoadLibraryW instead of LoadLibrary (patch by DougT). r+sr=vlad
[wine-gecko.git] / netwerk / protocol / http / src / nsHttpPipeline.cpp
blob7cfc675a49bc5d3b3dd9d2355fe0b2b241cf3d01
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
13 * License.
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.
22 * Contributor(s):
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 ***** */
39 #include <stdlib.h>
40 #include "nsHttp.h"
41 #include "nsHttpPipeline.h"
42 #include "nsHttpHandler.h"
43 #include "nsIOService.h"
44 #include "nsIRequest.h"
45 #include "nsISocketTransport.h"
46 #include "nsIStringStream.h"
47 #include "nsIPipe.h"
48 #include "nsCOMPtr.h"
49 #include "nsComponentManagerUtils.h"
50 #include "nsAutoLock.h"
52 #ifdef DEBUG
53 #include "prthread.h"
54 // defined by the socket transport service while active
55 extern PRThread *gSocketThread;
56 #endif
58 //-----------------------------------------------------------------------------
59 // nsHttpPushBackWriter
60 //-----------------------------------------------------------------------------
62 class nsHttpPushBackWriter : public nsAHttpSegmentWriter
64 public:
65 nsHttpPushBackWriter(const char *buf, PRUint32 bufLen)
66 : mBuf(buf)
67 , mBufLen(bufLen)
68 { }
69 virtual ~nsHttpPushBackWriter() {}
71 nsresult OnWriteSegment(char *buf, PRUint32 count, PRUint32 *countWritten)
73 if (mBufLen == 0)
74 return NS_BASE_STREAM_CLOSED;
76 if (count > mBufLen)
77 count = mBufLen;
79 memcpy(buf, mBuf, count);
81 mBuf += count;
82 mBufLen -= count;
83 *countWritten = count;
84 return NS_OK;
87 private:
88 const char *mBuf;
89 PRUint32 mBufLen;
92 //-----------------------------------------------------------------------------
93 // nsHttpPipeline <public>
94 //-----------------------------------------------------------------------------
96 nsHttpPipeline::nsHttpPipeline()
97 : mConnection(nsnull)
98 , mStatus(NS_OK)
99 , mRequestIsPartial(PR_FALSE)
100 , mResponseIsPartial(PR_FALSE)
101 , mClosed(PR_FALSE)
102 , mPushBackBuf(nsnull)
103 , mPushBackLen(0)
104 , mPushBackMax(0)
108 nsHttpPipeline::~nsHttpPipeline()
110 // make sure we aren't still holding onto any transactions!
111 Close(NS_ERROR_ABORT);
113 if (mPushBackBuf)
114 free(mPushBackBuf);
117 nsresult
118 nsHttpPipeline::AddTransaction(nsAHttpTransaction *trans)
120 LOG(("nsHttpPipeline::AddTransaction [this=%x trans=%x]\n", this, trans));
122 NS_ADDREF(trans);
123 mRequestQ.AppendElement(trans);
125 if (mConnection) {
126 trans->SetConnection(this);
128 if (mRequestQ.Count() == 1)
129 mConnection->ResumeSend();
132 return NS_OK;
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)
145 NS_INTERFACE_MAP_END
148 //-----------------------------------------------------------------------------
149 // nsHttpPipeline::nsAHttpConnection
150 //-----------------------------------------------------------------------------
152 nsresult
153 nsHttpPipeline::OnHeadersAvailable(nsAHttpTransaction *trans,
154 nsHttpRequestHead *requestHead,
155 nsHttpResponseHead *responseHead,
156 PRBool *reset)
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);
167 nsresult
168 nsHttpPipeline::ResumeSend()
170 NS_NOTREACHED("nsHttpPipeline::ResumeSend");
171 return NS_ERROR_UNEXPECTED;
174 nsresult
175 nsHttpPipeline::ResumeRecv()
177 NS_ASSERTION(PR_GetCurrentThread() == gSocketThread, "wrong thread");
178 NS_ASSERTION(mConnection, "no connection");
179 return mConnection->ResumeRecv();
182 void
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"
193 PRInt32 index;
194 PRBool killPipeline = PR_FALSE;
196 index = mRequestQ.IndexOf(trans);
197 if (index >= 0) {
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);
205 else {
206 index = mResponseQ.IndexOf(trans);
207 if (index >= 0)
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);
217 NS_RELEASE(trans);
219 if (killPipeline) {
220 if (mConnection)
221 mConnection->CloseTransaction(this, reason);
222 else
223 Close(reason);
227 void
228 nsHttpPipeline::GetConnectionInfo(nsHttpConnectionInfo **result)
230 NS_ASSERTION(mConnection, "no connection");
231 mConnection->GetConnectionInfo(result);
234 void
235 nsHttpPipeline::GetSecurityInfo(nsISupports **result)
237 NS_ASSERTION(mConnection, "no connection");
238 mConnection->GetSecurityInfo(result);
241 PRBool
242 nsHttpPipeline::IsPersistent()
244 return PR_TRUE; // pipelining requires this
247 PRBool
248 nsHttpPipeline::IsReused()
250 return PR_TRUE; // pipelining requires this
253 nsresult
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.
272 if (!mPushBackBuf) {
273 mPushBackMax = length;
274 mPushBackBuf = (char *) malloc(mPushBackMax);
275 if (!mPushBackBuf)
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);
283 if (!mPushBackBuf)
284 return NS_ERROR_OUT_OF_MEMORY;
287 memcpy(mPushBackBuf, data, length);
288 mPushBackLen = length;
290 return NS_OK;
293 //-----------------------------------------------------------------------------
294 // nsHttpPipeline::nsAHttpConnection
295 //-----------------------------------------------------------------------------
297 void
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);
312 void
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);
319 if (trans)
320 trans->GetSecurityCallbacks(result);
321 else
322 *result = nsnull;
325 void
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;
334 switch (status) {
335 case NS_NET_STATUS_RECEIVING_FROM:
336 // forward this only to the transaction currently recieving data
337 trans = Response(0);
338 if (trans)
339 trans->OnTransportStatus(status, progress);
340 break;
341 default:
342 // forward other notifications to all transactions
343 PRInt32 i, count = mRequestQ.Count();
344 for (i=0; i<count; ++i) {
345 trans = Request(i);
346 if (trans)
347 trans->OnTransportStatus(status, progress);
349 break;
353 PRBool
354 nsHttpPipeline::IsDone()
356 return (mRequestQ.Count() == 0) && (mResponseQ.Count() == 0);
359 nsresult
360 nsHttpPipeline::Status()
362 return mStatus;
365 PRUint32
366 nsHttpPipeline::Available()
368 PRUint32 result = 0;
370 PRInt32 i, count = mRequestQ.Count();
371 for (i=0; i<count; ++i)
372 result += Request(i)->Available();
373 return result;
376 NS_METHOD
377 nsHttpPipeline::ReadFromPipe(nsIInputStream *stream,
378 void *closure,
379 const char *buf,
380 PRUint32 offset,
381 PRUint32 count,
382 PRUint32 *countRead)
384 nsHttpPipeline *self = (nsHttpPipeline *) closure;
385 return self->mReader->OnReadSegment(buf, count, countRead);
388 nsresult
389 nsHttpPipeline::ReadSegments(nsAHttpSegmentReader *reader,
390 PRUint32 count,
391 PRUint32 *countRead)
393 LOG(("nsHttpPipeline::ReadSegments [this=%x count=%u]\n", this, count));
395 NS_ASSERTION(PR_GetCurrentThread() == gSocketThread, "wrong thread");
397 if (mClosed) {
398 *countRead = 0;
399 return mStatus;
402 nsresult rv;
403 PRUint32 avail = 0;
404 if (mSendBufIn) {
405 rv = mSendBufIn->Available(&avail);
406 if (NS_FAILED(rv)) return rv;
409 if (avail == 0) {
410 rv = FillSendBuf();
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
417 if (avail == 0) {
418 *countRead = 0;
419 return NS_OK;
423 // read no more than what was requested
424 if (avail > count)
425 avail = count;
427 mReader = reader;
429 rv = mSendBufIn->ReadSegments(ReadFromPipe, this, avail, countRead);
431 mReader = nsnull;
432 return rv;
435 nsresult
436 nsHttpPipeline::WriteSegments(nsAHttpSegmentWriter *writer,
437 PRUint32 count,
438 PRUint32 *countWritten)
440 LOG(("nsHttpPipeline::WriteSegments [this=%x count=%u]\n", this, count));
442 NS_ASSERTION(PR_GetCurrentThread() == gSocketThread, "wrong thread");
444 if (mClosed)
445 return NS_SUCCEEDED(mStatus) ? NS_BASE_STREAM_CLOSED : mStatus;
447 nsAHttpTransaction *trans;
448 nsresult rv;
450 trans = Response(0);
451 if (!trans) {
452 if (mRequestQ.Count() > 0)
453 rv = NS_BASE_STREAM_WOULD_BLOCK;
454 else
455 rv = NS_BASE_STREAM_CLOSED;
457 else {
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()) {
465 trans->Close(NS_OK);
466 NS_RELEASE(trans);
467 mResponseQ.RemoveElementAt(0);
468 mResponseIsPartial = PR_FALSE;
470 // ask the connection manager to add additional transactions
471 // to our pipeline.
472 gHttpHandler->ConnMgr()->AddTransactionToPipeline(this);
474 else
475 mResponseIsPartial = PR_TRUE;
478 if (mPushBackLen) {
479 nsHttpPushBackWriter writer(mPushBackBuf, mPushBackLen);
480 PRUint32 len = mPushBackLen, n;
481 mPushBackLen = 0;
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);
488 return rv;
491 void
492 nsHttpPipeline::Close(nsresult reason)
494 LOG(("nsHttpPipeline::Close [this=%x reason=%x]\n", this, reason));
496 if (mClosed) {
497 LOG((" already closed\n"));
498 return;
501 // the connection is going away!
502 mStatus = reason;
503 mClosed = PR_TRUE;
505 // we must no longer reference the connection!
506 NS_IF_RELEASE(mConnection);
508 PRUint32 i, count;
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) {
514 trans = Request(i);
515 trans->Close(NS_ERROR_NET_RESET);
516 NS_RELEASE(trans);
518 mRequestQ.Clear();
520 trans = Response(0);
521 if (trans) {
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);
526 else
527 trans->Close(NS_ERROR_NET_RESET);
528 NS_RELEASE(trans);
530 // any remaining pending responses can be restarted
531 count = mResponseQ.Count();
532 for (i=1; i<count; ++i) {
533 trans = Response(i);
534 trans->Close(NS_ERROR_NET_RESET);
535 NS_RELEASE(trans);
537 mResponseQ.Clear();
541 nsresult
542 nsHttpPipeline::OnReadSegment(const char *segment,
543 PRUint32 count,
544 PRUint32 *countRead)
546 return mSendBufOut->Write(segment, count, countRead);
549 nsresult
550 nsHttpPipeline::FillSendBuf()
552 // reads from request queue, moving transactions to response queue
553 // when they have been completely read.
555 nsresult rv;
557 if (!mSendBufIn) {
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,
563 PR_TRUE, PR_TRUE,
564 nsIOService::gBufferCache);
565 if (NS_FAILED(rv)) return rv;
568 PRUint32 n, avail;
569 nsAHttpTransaction *trans;
570 while ((trans = Request(0)) != nsnull) {
571 avail = trans->Available();
572 if (avail) {
573 rv = trans->ReadSegments(this, avail, &n);
574 if (NS_FAILED(rv)) return rv;
576 if (n == 0) {
577 LOG(("send pipe is full"));
578 break;
581 avail = trans->Available();
582 if (avail == 0) {
583 // move transaction from request queue to response queue
584 mRequestQ.RemoveElementAt(0);
585 mResponseQ.AppendElement(trans);
586 mRequestIsPartial = PR_FALSE;
588 else
589 mRequestIsPartial = PR_TRUE;
591 return NS_OK;