1 : /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 : /* vim:set ts=4 sw=4 sts=4 et cin: */
3 : /* ***** BEGIN LICENSE BLOCK *****
4 : * Version: MPL 1.1/GPL 2.0/LGPL 2.1
5 : *
6 : * The contents of this file are subject to the Mozilla Public License Version
7 : * 1.1 (the "License"); you may not use this file except in compliance with
8 : * the License. You may obtain a copy of the License at
9 : * http://www.mozilla.org/MPL/
10 : *
11 : * Software distributed under the License is distributed on an "AS IS" basis,
12 : * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
13 : * for the specific language governing rights and limitations under the
14 : * License.
15 : *
16 : * The Original Code is Mozilla.
17 : *
18 : * The Initial Developer of the Original Code is
19 : * Netscape Communications.
20 : * Portions created by the Initial Developer are Copyright (C) 2001
21 : * the Initial Developer. All Rights Reserved.
22 : *
23 : * Contributor(s):
24 : * Darin Fisher <darin@netscape.com> (original author)
25 : *
26 : * Alternatively, the contents of this file may be used under the terms of
27 : * either the GNU General Public License Version 2 or later (the "GPL"), or
28 : * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
29 : * in which case the provisions of the GPL or the LGPL are applicable instead
30 : * of those above. If you wish to allow use of your version of this file only
31 : * under the terms of either the GPL or the LGPL, and not to allow others to
32 : * use your version of this file under the terms of the MPL, indicate your
33 : * decision by deleting the provisions above and replace them with the notice
34 : * and other provisions required by the GPL or the LGPL. If you do not delete
35 : * the provisions above, a recipient may use your version of this file under
36 : * the terms of any one of the MPL, the GPL or the LGPL.
37 : *
38 : * ***** END LICENSE BLOCK ***** */
39 :
40 : #include "nsHttpConnection.h"
41 : #include "nsHttpTransaction.h"
42 : #include "nsHttpRequestHead.h"
43 : #include "nsHttpResponseHead.h"
44 : #include "nsHttpHandler.h"
45 : #include "nsIOService.h"
46 : #include "nsISocketTransportService.h"
47 : #include "nsISocketTransport.h"
48 : #include "nsIServiceManager.h"
49 : #include "nsISSLSocketControl.h"
50 : #include "nsStringStream.h"
51 : #include "netCore.h"
52 : #include "nsNetCID.h"
53 : #include "nsProxyRelease.h"
54 : #include "prmem.h"
55 : #include "nsPreloadedStream.h"
56 : #include "SpdySession.h"
57 : #include "mozilla/Telemetry.h"
58 : #include "nsISupportsPriority.h"
59 :
60 : #ifdef DEBUG
61 : // defined by the socket transport service while active
62 : extern PRThread *gSocketThread;
63 : #endif
64 :
65 : static NS_DEFINE_CID(kSocketTransportServiceCID, NS_SOCKETTRANSPORTSERVICE_CID);
66 :
67 : using namespace mozilla::net;
68 :
69 : //-----------------------------------------------------------------------------
70 : // nsHttpConnection <public>
71 : //-----------------------------------------------------------------------------
72 :
73 2976 : nsHttpConnection::nsHttpConnection()
74 : : mTransaction(nsnull)
75 : , mLastReadTime(0)
76 : , mIdleTimeout(0)
77 : , mConsiderReusedAfterInterval(0)
78 : , mConsiderReusedAfterEpoch(0)
79 : , mCurrentBytesRead(0)
80 : , mMaxBytesRead(0)
81 : , mTotalBytesRead(0)
82 : , mKeepAlive(true) // assume to keep-alive by default
83 : , mKeepAliveMask(true)
84 : , mSupportsPipelining(false) // assume low-grade server
85 : , mIsReused(false)
86 : , mCompletedProxyConnect(false)
87 : , mLastTransactionExpectedNoContent(false)
88 : , mIdleMonitoring(false)
89 : , mHttp1xTransactionCount(0)
90 : , mNPNComplete(false)
91 : , mSetupNPNCalled(false)
92 : , mUsingSpdy(false)
93 : , mPriority(nsISupportsPriority::PRIORITY_NORMAL)
94 : , mReportedSpdy(false)
95 2976 : , mEverUsedSpdy(false)
96 : {
97 2976 : LOG(("Creating nsHttpConnection @%x\n", this));
98 :
99 : // grab a reference to the handler to ensure that it doesn't go away.
100 2976 : nsHttpHandler *handler = gHttpHandler;
101 2976 : NS_ADDREF(handler);
102 2976 : }
103 :
104 8928 : nsHttpConnection::~nsHttpConnection()
105 : {
106 2976 : LOG(("Destroying nsHttpConnection @%x\n", this));
107 :
108 2976 : if (mCallbacks) {
109 1 : nsIInterfaceRequestor *cbs = nsnull;
110 1 : mCallbacks.swap(cbs);
111 1 : NS_ProxyRelease(mCallbackTarget, cbs);
112 : }
113 :
114 : // release our reference to the handler
115 2976 : nsHttpHandler *handler = gHttpHandler;
116 2976 : NS_RELEASE(handler);
117 :
118 2976 : if (!mEverUsedSpdy) {
119 2976 : LOG(("nsHttpConnection %p performed %d HTTP/1.x transactions\n",
120 : this, mHttp1xTransactionCount));
121 : mozilla::Telemetry::Accumulate(
122 2976 : mozilla::Telemetry::HTTP_REQUEST_PER_CONN, mHttp1xTransactionCount);
123 : }
124 :
125 2976 : if (mTotalBytesRead) {
126 2499 : PRUint32 totalKBRead = static_cast<PRUint32>(mTotalBytesRead >> 10);
127 2499 : LOG(("nsHttpConnection %p read %dkb on connection spdy=%d\n",
128 : this, totalKBRead, mEverUsedSpdy));
129 : mozilla::Telemetry::Accumulate(
130 : mEverUsedSpdy ?
131 : mozilla::Telemetry::SPDY_KBREAD_PER_CONN :
132 : mozilla::Telemetry::HTTP_KBREAD_PER_CONN,
133 2499 : totalKBRead);
134 : }
135 11904 : }
136 :
137 : nsresult
138 2976 : nsHttpConnection::Init(nsHttpConnectionInfo *info,
139 : PRUint16 maxHangTime,
140 : nsISocketTransport *transport,
141 : nsIAsyncInputStream *instream,
142 : nsIAsyncOutputStream *outstream,
143 : nsIInterfaceRequestor *callbacks,
144 : nsIEventTarget *callbackTarget)
145 : {
146 2976 : NS_ABORT_IF_FALSE(transport && instream && outstream,
147 : "invalid socket information");
148 2976 : LOG(("nsHttpConnection::Init [this=%p "
149 : "transport=%p instream=%p outstream=%p]\n",
150 : this, transport, instream, outstream));
151 :
152 2976 : NS_ENSURE_ARG_POINTER(info);
153 2976 : NS_ENSURE_TRUE(!mConnInfo, NS_ERROR_ALREADY_INITIALIZED);
154 :
155 2976 : mConnInfo = info;
156 2976 : mMaxHangTime = PR_SecondsToInterval(maxHangTime);
157 2976 : mLastReadTime = PR_IntervalNow();
158 :
159 2976 : mSocketTransport = transport;
160 2976 : mSocketIn = instream;
161 2976 : mSocketOut = outstream;
162 2976 : nsresult rv = mSocketTransport->SetEventSink(this, nsnull);
163 2976 : NS_ENSURE_SUCCESS(rv, rv);
164 :
165 2976 : mCallbacks = callbacks;
166 2976 : mCallbackTarget = callbackTarget;
167 2976 : rv = mSocketTransport->SetSecurityCallbacks(this);
168 2976 : NS_ENSURE_SUCCESS(rv, rv);
169 :
170 2976 : return NS_OK;
171 : }
172 :
173 : void
174 0 : nsHttpConnection::StartSpdy()
175 : {
176 0 : LOG(("nsHttpConnection::StartSpdy [this=%p]\n", this));
177 :
178 0 : NS_ABORT_IF_FALSE(!mSpdySession, "mSpdySession should be null");
179 :
180 0 : mUsingSpdy = true;
181 0 : mEverUsedSpdy = true;
182 :
183 : // Setting the connection as reused allows some transactions that fail
184 : // with NS_ERROR_NET_RESET to be restarted and SPDY uses that code
185 : // to handle clean rejections (such as those that arrived after
186 : // a server goaway was generated).
187 0 : mIsReused = true;
188 :
189 : // If mTransaction is a pipeline object it might represent
190 : // several requests. If so, we need to unpack that and
191 : // pack them all into a new spdy session.
192 :
193 0 : nsTArray<nsRefPtr<nsAHttpTransaction> > list;
194 0 : nsresult rv = mTransaction->TakeSubTransactions(list);
195 :
196 0 : if (rv == NS_ERROR_ALREADY_OPENED) {
197 : // Has the interface for TakeSubTransactions() changed?
198 0 : LOG(("TakeSubTranscations somehow called after "
199 : "nsAHttpTransaction began processing\n"));
200 0 : NS_ABORT_IF_FALSE(false,
201 : "TakeSubTranscations somehow called after "
202 : "nsAHttpTransaction began processing");
203 0 : mTransaction->Close(NS_ERROR_ABORT);
204 : return;
205 : }
206 :
207 0 : if (NS_FAILED(rv) && rv != NS_ERROR_NOT_IMPLEMENTED) {
208 : // Has the interface for TakeSubTransactions() changed?
209 0 : LOG(("unexpected rv from nnsAHttpTransaction::TakeSubTransactions()"));
210 0 : NS_ABORT_IF_FALSE(false,
211 : "unexpected result from "
212 : "nsAHttpTransaction::TakeSubTransactions()");
213 0 : mTransaction->Close(NS_ERROR_ABORT);
214 : return;
215 : }
216 :
217 0 : if (NS_FAILED(rv)) { // includes NS_ERROR_NOT_IMPLEMENTED
218 0 : NS_ABORT_IF_FALSE(list.IsEmpty(), "sub transaction list not empty");
219 :
220 : // This is ok - treat mTransaction as a single real request.
221 : // Wrap the old http transaction into the new spdy session
222 : // as the first stream.
223 : mSpdySession = new SpdySession(mTransaction,
224 : mSocketTransport,
225 0 : mPriority);
226 0 : LOG(("nsHttpConnection::StartSpdy moves single transaction %p "
227 : "into SpdySession %p\n", mTransaction.get(), mSpdySession.get()));
228 : }
229 : else {
230 0 : NS_ABORT_IF_FALSE(!list.IsEmpty(), "sub transaction list empty");
231 :
232 0 : PRInt32 count = list.Length();
233 :
234 0 : LOG(("nsHttpConnection::StartSpdy moving transaction list len=%d "
235 : "into SpdySession %p\n", count, mSpdySession.get()));
236 :
237 0 : for (PRInt32 index = 0; index < count; ++index) {
238 0 : if (!mSpdySession) {
239 0 : mSpdySession = new SpdySession(list[index],
240 : mSocketTransport,
241 0 : mPriority);
242 : }
243 : else {
244 : // AddStream() cannot fail
245 0 : if (!mSpdySession->AddStream(list[index], mPriority)) {
246 0 : NS_ABORT_IF_FALSE(false, "SpdySession::AddStream failed");
247 0 : LOG(("SpdySession::AddStream failed\n"));
248 0 : mTransaction->Close(NS_ERROR_ABORT);
249 : return;
250 : }
251 : }
252 : }
253 : }
254 :
255 0 : mSupportsPipelining = false; // dont use http/1 pipelines with spdy
256 0 : mTransaction = mSpdySession;
257 0 : mIdleTimeout = gHttpHandler->SpdyTimeout();
258 : }
259 :
260 : bool
261 5824 : nsHttpConnection::EnsureNPNComplete()
262 : {
263 : // NPN is only used by SPDY right now.
264 : //
265 : // If for some reason the components to check on NPN aren't available,
266 : // this function will just return true to continue on and disable SPDY
267 :
268 5824 : if (!mSocketTransport) {
269 : // this cannot happen
270 0 : NS_ABORT_IF_FALSE(false,
271 : "EnsureNPNComplete socket transport precondition");
272 0 : mNPNComplete = true;
273 0 : return true;
274 : }
275 :
276 5824 : if (mNPNComplete)
277 5808 : return true;
278 :
279 : nsresult rv;
280 :
281 32 : nsCOMPtr<nsISupports> securityInfo;
282 32 : nsCOMPtr<nsISSLSocketControl> ssl;
283 32 : nsCAutoString negotiatedNPN;
284 :
285 16 : rv = mSocketTransport->GetSecurityInfo(getter_AddRefs(securityInfo));
286 16 : if (NS_FAILED(rv))
287 0 : goto npnComplete;
288 :
289 16 : ssl = do_QueryInterface(securityInfo, &rv);
290 16 : if (NS_FAILED(rv))
291 0 : goto npnComplete;
292 :
293 16 : rv = ssl->GetNegotiatedNPN(negotiatedNPN);
294 16 : if (rv == NS_ERROR_NOT_CONNECTED) {
295 :
296 : // By writing 0 bytes to the socket the SSL handshake machine is
297 : // pushed forward.
298 12 : PRUint32 count = 0;
299 12 : rv = mSocketOut->Write("", 0, &count);
300 :
301 12 : if (NS_FAILED(rv) && rv != NS_BASE_STREAM_WOULD_BLOCK)
302 0 : goto npnComplete;
303 12 : return false;
304 : }
305 :
306 4 : if (NS_FAILED(rv))
307 0 : goto npnComplete;
308 :
309 4 : LOG(("nsHttpConnection::EnsureNPNComplete %p negotiated to '%s'",
310 : this, negotiatedNPN.get()));
311 :
312 4 : if (negotiatedNPN.Equals(NS_LITERAL_CSTRING("spdy/2")))
313 0 : StartSpdy();
314 :
315 : mozilla::Telemetry::Accumulate(mozilla::Telemetry::SPDY_NPN_CONNECT,
316 4 : mUsingSpdy);
317 :
318 : npnComplete:
319 4 : LOG(("nsHttpConnection::EnsureNPNComplete setting complete to true"));
320 4 : mNPNComplete = true;
321 4 : return true;
322 : }
323 :
324 : // called on the socket thread
325 : nsresult
326 2975 : nsHttpConnection::Activate(nsAHttpTransaction *trans, PRUint8 caps, PRInt32 pri)
327 : {
328 : nsresult rv;
329 :
330 2975 : NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread");
331 2975 : LOG(("nsHttpConnection::Activate [this=%x trans=%x caps=%x]\n",
332 : this, trans, caps));
333 :
334 2975 : mPriority = pri;
335 2975 : if (mTransaction && mUsingSpdy)
336 0 : return AddTransaction(trans, pri);
337 :
338 2975 : NS_ENSURE_ARG_POINTER(trans);
339 2975 : NS_ENSURE_TRUE(!mTransaction, NS_ERROR_IN_PROGRESS);
340 :
341 : // Update security callbacks
342 5950 : nsCOMPtr<nsIInterfaceRequestor> callbacks;
343 5950 : nsCOMPtr<nsIEventTarget> callbackTarget;
344 2975 : trans->GetSecurityCallbacks(getter_AddRefs(callbacks),
345 5950 : getter_AddRefs(callbackTarget));
346 2975 : if (callbacks != mCallbacks) {
347 0 : mCallbacks.swap(callbacks);
348 0 : if (callbacks)
349 0 : NS_ProxyRelease(mCallbackTarget, callbacks);
350 0 : mCallbackTarget = callbackTarget;
351 : }
352 :
353 2975 : SetupNPN(caps); // only for spdy
354 :
355 : // take ownership of the transaction
356 2975 : mTransaction = trans;
357 :
358 2975 : NS_ABORT_IF_FALSE(!mIdleMonitoring,
359 : "Activating a connection with an Idle Monitor");
360 2975 : mIdleMonitoring = false;
361 :
362 : // set mKeepAlive according to what will be requested
363 2975 : mKeepAliveMask = mKeepAlive = (caps & NS_HTTP_ALLOW_KEEPALIVE);
364 :
365 : // need to handle HTTP CONNECT tunnels if this is the first time if
366 : // we are tunneling through a proxy
367 5950 : if (((mConnInfo->UsingSSL() && mConnInfo->UsingHttpProxy()) ||
368 2975 : mConnInfo->ShouldForceConnectMethod()) && !mCompletedProxyConnect) {
369 0 : rv = SetupProxyConnect();
370 0 : if (NS_FAILED(rv))
371 0 : goto failed_activation;
372 : }
373 :
374 : // Clear the per activation counter
375 2975 : mCurrentBytesRead = 0;
376 :
377 : // The overflow state is not needed between activations
378 2975 : mInputOverflow = nsnull;
379 :
380 2975 : rv = OnOutputStreamReady(mSocketOut);
381 :
382 : failed_activation:
383 2975 : if (NS_FAILED(rv)) {
384 0 : mTransaction = nsnull;
385 : }
386 :
387 2975 : return rv;
388 : }
389 :
390 : void
391 2981 : nsHttpConnection::SetupNPN(PRUint8 caps)
392 : {
393 2981 : if (mSetupNPNCalled) /* do only once */
394 5 : return;
395 2976 : mSetupNPNCalled = true;
396 :
397 : // Setup NPN Negotiation if necessary (only for SPDY)
398 2976 : if (!mNPNComplete) {
399 :
400 2976 : mNPNComplete = true;
401 :
402 2988 : if (mConnInfo->UsingSSL() &&
403 4 : !(caps & NS_HTTP_DISALLOW_SPDY) &&
404 4 : !mConnInfo->UsingHttpProxy() &&
405 4 : gHttpHandler->IsSpdyEnabled()) {
406 4 : LOG(("nsHttpConnection::Init Setting up SPDY Negotiation"));
407 8 : nsCOMPtr<nsISupports> securityInfo;
408 : nsresult rv =
409 4 : mSocketTransport->GetSecurityInfo(getter_AddRefs(securityInfo));
410 4 : if (NS_FAILED(rv))
411 : return;
412 :
413 : nsCOMPtr<nsISSLSocketControl> ssl =
414 8 : do_QueryInterface(securityInfo, &rv);
415 4 : if (NS_FAILED(rv))
416 : return;
417 :
418 12 : nsTArray<nsCString> protocolArray;
419 4 : protocolArray.AppendElement(NS_LITERAL_CSTRING("spdy/2"));
420 4 : protocolArray.AppendElement(NS_LITERAL_CSTRING("http/1.1"));
421 4 : if (NS_SUCCEEDED(ssl->SetNPNList(protocolArray))) {
422 4 : LOG(("nsHttpConnection::Init Setting up SPDY Negotiation OK"));
423 4 : mNPNComplete = false;
424 : }
425 : }
426 : }
427 : }
428 :
429 : void
430 2819 : nsHttpConnection::HandleAlternateProtocol(nsHttpResponseHead *responseHead)
431 : {
432 : // Look for the Alternate-Protocol header. Alternate-Protocol is
433 : // essentially a way to rediect future transactions from http to
434 : // spdy.
435 : //
436 :
437 2819 : if (!gHttpHandler->IsSpdyEnabled() || mUsingSpdy)
438 0 : return;
439 :
440 2819 : const char *val = responseHead->PeekHeader(nsHttp::Alternate_Protocol);
441 2819 : if (!val)
442 2819 : return;
443 :
444 : // The spec allows redirections to any port, but due to concerns over
445 : // silently redirecting to stealth ports we only allow port 443
446 : //
447 : // Alternate-Protocol: 5678:somethingelse, 443:npn-spdy/2
448 :
449 0 : if (nsHttp::FindToken(val, "443:npn-spdy/2", HTTP_HEADER_VALUE_SEPS)) {
450 0 : LOG(("Connection %p Transaction %p found Alternate-Protocol "
451 : "header %s", this, mTransaction.get(), val));
452 0 : gHttpHandler->ConnMgr()->ReportSpdyAlternateProtocol(this);
453 : }
454 : }
455 :
456 : nsresult
457 0 : nsHttpConnection::AddTransaction(nsAHttpTransaction *httpTransaction,
458 : PRInt32 priority)
459 : {
460 0 : LOG(("nsHttpConnection::AddTransaction for SPDY"));
461 :
462 0 : NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread");
463 0 : NS_ABORT_IF_FALSE(mSpdySession && mUsingSpdy,
464 : "AddTransaction to live http connection without spdy");
465 0 : NS_ABORT_IF_FALSE(mTransaction,
466 : "AddTransaction to idle http connection");
467 :
468 0 : if (!mSpdySession->AddStream(httpTransaction, priority)) {
469 0 : NS_ABORT_IF_FALSE(0, "AddStream should never fail due to"
470 : "RoomForMore() admission check");
471 0 : return NS_ERROR_FAILURE;
472 : }
473 :
474 0 : ResumeSend();
475 :
476 0 : return NS_OK;
477 : }
478 :
479 : void
480 3137 : nsHttpConnection::Close(nsresult reason)
481 : {
482 3137 : LOG(("nsHttpConnection::Close [this=%x reason=%x]\n", this, reason));
483 :
484 3137 : NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread");
485 :
486 3137 : if (NS_FAILED(reason)) {
487 3137 : if (mIdleMonitoring)
488 5 : EndIdleMonitoring();
489 :
490 3137 : if (mSocketTransport) {
491 3137 : mSocketTransport->SetSecurityCallbacks(nsnull);
492 3137 : mSocketTransport->SetEventSink(nsnull, nsnull);
493 3137 : mSocketTransport->Close(reason);
494 : }
495 3137 : mKeepAlive = false;
496 : }
497 3137 : }
498 :
499 : // called on the socket thread
500 : nsresult
501 0 : nsHttpConnection::ProxyStartSSL()
502 : {
503 0 : LOG(("nsHttpConnection::ProxyStartSSL [this=%x]\n", this));
504 : #ifdef DEBUG
505 0 : NS_PRECONDITION(PR_GetCurrentThread() == gSocketThread, "wrong thread");
506 : #endif
507 :
508 0 : nsCOMPtr<nsISupports> securityInfo;
509 0 : nsresult rv = mSocketTransport->GetSecurityInfo(getter_AddRefs(securityInfo));
510 0 : if (NS_FAILED(rv)) return rv;
511 :
512 0 : nsCOMPtr<nsISSLSocketControl> ssl = do_QueryInterface(securityInfo, &rv);
513 0 : if (NS_FAILED(rv)) return rv;
514 :
515 0 : return ssl->ProxyStartSSL();
516 : }
517 :
518 : void
519 1 : nsHttpConnection::DontReuse()
520 : {
521 1 : mKeepAliveMask = false;
522 1 : mKeepAlive = false;
523 1 : mIdleTimeout = 0;
524 1 : if (mSpdySession)
525 0 : mSpdySession->DontReuse();
526 1 : }
527 :
528 : bool
529 2978 : nsHttpConnection::CanReuse()
530 : {
531 : bool canReuse;
532 :
533 2978 : if (mUsingSpdy)
534 0 : canReuse = mSpdySession->CanReuse();
535 : else
536 2978 : canReuse = IsKeepAlive();
537 :
538 2978 : canReuse = canReuse && (IdleTime() < mIdleTimeout) && IsAlive();
539 :
540 : // An idle persistent connection should not have data waiting to be read
541 : // before a request is sent. Data here is likely a 408 timeout response
542 : // which we would deal with later on through the restart logic, but that
543 : // path is more expensive than just closing the socket now.
544 :
545 : PRUint32 dataSize;
546 2984 : if (canReuse && mSocketIn && !mUsingSpdy &&
547 6 : NS_SUCCEEDED(mSocketIn->Available(&dataSize)) && dataSize) {
548 0 : LOG(("nsHttpConnection::CanReuse %p %s"
549 : "Socket not reusable because read data pending (%d) on it.\n",
550 : this, mConnInfo->Host(), dataSize));
551 0 : canReuse = false;
552 : }
553 2978 : return canReuse;
554 : }
555 :
556 : bool
557 0 : nsHttpConnection::CanDirectlyActivate()
558 : {
559 : // return true if a new transaction can be addded to ths connection at any
560 : // time through Activate(). In practice this means this is a healthy SPDY
561 : // connection with room for more concurrent streams.
562 :
563 0 : return UsingSpdy() && CanReuse() && mSpdySession->RoomForMoreStreams();
564 : }
565 :
566 : PRIntervalTime
567 19 : nsHttpConnection::IdleTime()
568 : {
569 : return mSpdySession ?
570 19 : mSpdySession->IdleTime() : (PR_IntervalNow() - mLastReadTime);
571 : }
572 :
573 : // returns the number of seconds left before the allowable idle period
574 : // expires, or 0 if the period has already expied.
575 : PRUint32
576 6 : nsHttpConnection::TimeToLive()
577 : {
578 6 : if (IdleTime() >= mIdleTimeout)
579 0 : return 0;
580 6 : PRUint32 timeToLive = PR_IntervalToSeconds(mIdleTimeout - IdleTime());
581 :
582 : // a positive amount of time can be rounded to 0. Because 0 is used
583 : // as the expiration signal, round all values from 0 to 1 up to 1.
584 6 : if (!timeToLive)
585 1 : timeToLive = 1;
586 6 : return timeToLive;
587 : }
588 :
589 : bool
590 6 : nsHttpConnection::IsAlive()
591 : {
592 6 : if (!mSocketTransport)
593 0 : return false;
594 :
595 : // SocketTransport::IsAlive can run the SSL state machine, so make sure
596 : // the NPN options are set before that happens.
597 6 : SetupNPN(0);
598 :
599 : bool alive;
600 6 : nsresult rv = mSocketTransport->IsAlive(&alive);
601 6 : if (NS_FAILED(rv))
602 0 : alive = false;
603 :
604 : //#define TEST_RESTART_LOGIC
605 : #ifdef TEST_RESTART_LOGIC
606 : if (!alive) {
607 : LOG(("pretending socket is still alive to test restart logic\n"));
608 : alive = true;
609 : }
610 : #endif
611 :
612 6 : return alive;
613 : }
614 :
615 : bool
616 4 : nsHttpConnection::SupportsPipelining(nsHttpResponseHead *responseHead)
617 : {
618 : // SPDY supports infinite parallelism, so no need to pipeline.
619 4 : if (mUsingSpdy)
620 0 : return false;
621 :
622 : // XXX there should be a strict mode available that disables this
623 : // blacklisting.
624 :
625 : // assuming connection is HTTP/1.1 with keep-alive enabled
626 4 : if (mConnInfo->UsingHttpProxy() && !mConnInfo->UsingSSL()) {
627 : // XXX check for bad proxy servers...
628 0 : return true;
629 : }
630 :
631 : // XXX what about checking for a Via header? (transparent proxies)
632 :
633 : // check for bad origin servers
634 4 : const char *val = responseHead->PeekHeader(nsHttp::Server);
635 4 : if (!val)
636 0 : return false; // no header, no love
637 :
638 : // The blacklist is indexed by the first character. All of these servers are
639 : // known to return their identifier as the first thing in the server string,
640 : // so we can do a leading match.
641 :
642 : static const char *bad_servers[26][6] = {
643 : { nsnull }, { nsnull }, { nsnull }, { nsnull }, // a - d
644 : { "EFAServer/", nsnull }, // e
645 : { nsnull }, { nsnull }, { nsnull }, { nsnull }, // f - i
646 : { nsnull }, { nsnull }, { nsnull }, // j - l
647 : { "Microsoft-IIS/4.", "Microsoft-IIS/5.", nsnull }, // m
648 : { "Netscape-Enterprise/3.", "Netscape-Enterprise/4.",
649 : "Netscape-Enterprise/5.", "Netscape-Enterprise/6.", nsnull }, // n
650 : { nsnull }, { nsnull }, { nsnull }, { nsnull }, // o - r
651 : { nsnull }, { nsnull }, { nsnull }, { nsnull }, // s - v
652 : { "WebLogic 3.", "WebLogic 4.","WebLogic 5.", "WebLogic 6.",
653 : "Winstone Servlet Engine v0.", nsnull }, // w
654 : { nsnull }, { nsnull }, { nsnull } // x - z
655 : };
656 :
657 4 : int index = val[0] - 'A'; // the whole table begins with capital letters
658 4 : if ((index >= 0) && (index <= 25))
659 : {
660 0 : for (int i = 0; bad_servers[index][i] != nsnull; i++) {
661 0 : if (!PL_strncmp (val, bad_servers[index][i], strlen (bad_servers[index][i]))) {
662 0 : LOG(("looks like this server does not support pipelining"));
663 0 : return false;
664 : }
665 : }
666 : }
667 :
668 : // ok, let's allow pipelining to this server
669 4 : return true;
670 : }
671 :
672 : //----------------------------------------------------------------------------
673 : // nsHttpConnection::nsAHttpConnection compatible methods
674 : //----------------------------------------------------------------------------
675 :
676 : nsresult
677 2819 : nsHttpConnection::OnHeadersAvailable(nsAHttpTransaction *trans,
678 : nsHttpRequestHead *requestHead,
679 : nsHttpResponseHead *responseHead,
680 : bool *reset)
681 : {
682 2819 : LOG(("nsHttpConnection::OnHeadersAvailable [this=%p trans=%p response-head=%p]\n",
683 : this, trans, responseHead));
684 :
685 2819 : NS_ASSERTION(PR_GetCurrentThread() == gSocketThread, "wrong thread");
686 2819 : NS_ENSURE_ARG_POINTER(trans);
687 2819 : NS_ASSERTION(responseHead, "No response head?");
688 :
689 : // If the server issued an explicit timeout, then we need to close down the
690 : // socket transport. We pass an error code of NS_ERROR_NET_RESET to
691 : // trigger the transactions 'restart' mechanism. We tell it to reset its
692 : // response headers so that it will be ready to receive the new response.
693 2819 : if (responseHead->Status() == 408) {
694 0 : Close(NS_ERROR_NET_RESET);
695 0 : *reset = true;
696 0 : return NS_OK;
697 : }
698 :
699 : // we won't change our keep-alive policy unless the server has explicitly
700 : // told us to do so.
701 :
702 : // inspect the connection headers for keep-alive info provided the
703 : // transaction completed successfully.
704 2819 : const char *val = responseHead->PeekHeader(nsHttp::Connection);
705 2819 : if (!val)
706 25 : val = responseHead->PeekHeader(nsHttp::Proxy_Connection);
707 :
708 : // reset to default (the server may have changed since we last checked)
709 2819 : mSupportsPipelining = false;
710 :
711 5606 : if ((responseHead->Version() < NS_HTTP_VERSION_1_1) ||
712 2787 : (requestHead->Version() < NS_HTTP_VERSION_1_1)) {
713 : // HTTP/1.0 connections are by default NOT persistent
714 32 : if (val && !PL_strcasecmp(val, "keep-alive"))
715 0 : mKeepAlive = true;
716 : else
717 32 : mKeepAlive = false;
718 : }
719 : else {
720 : // HTTP/1.1 connections are by default persistent
721 2787 : if (val && !PL_strcasecmp(val, "close"))
722 2783 : mKeepAlive = false;
723 : else {
724 4 : mKeepAlive = true;
725 :
726 : // Do not support pipelining when we are establishing
727 : // an SSL tunnel though an HTTP proxy. Pipelining support
728 : // determination must be based on comunication with the
729 : // target server in this case. See bug 422016 for futher
730 : // details.
731 4 : if (!mProxyConnectStream)
732 4 : mSupportsPipelining = SupportsPipelining(responseHead);
733 : }
734 : }
735 2819 : mKeepAliveMask = mKeepAlive;
736 :
737 : // if this connection is persistent, then the server may send a "Keep-Alive"
738 : // header specifying the maximum number of times the connection can be
739 : // reused as well as the maximum amount of time the connection can be idle
740 : // before the server will close it. we ignore the max reuse count, because
741 : // a "keep-alive" connection is by definition capable of being reused, and
742 : // we only care about being able to reuse it once. if a timeout is not
743 : // specified then we use our advertized timeout value.
744 2819 : if (mKeepAlive) {
745 4 : val = responseHead->PeekHeader(nsHttp::Keep_Alive);
746 :
747 4 : if (!mUsingSpdy) {
748 4 : const char *cp = PL_strcasestr(val, "timeout=");
749 4 : if (cp)
750 0 : mIdleTimeout = PR_SecondsToInterval((PRUint32) atoi(cp + 8));
751 : else
752 4 : mIdleTimeout = gHttpHandler->IdleTimeout();
753 : }
754 : else {
755 0 : mIdleTimeout = gHttpHandler->SpdyTimeout();
756 : }
757 :
758 4 : LOG(("Connection can be reused [this=%x idle-timeout=%usec]\n",
759 : this, PR_IntervalToSeconds(mIdleTimeout)));
760 : }
761 :
762 2819 : if (!mProxyConnectStream)
763 2819 : HandleAlternateProtocol(responseHead);
764 :
765 : // if we're doing an SSL proxy connect, then we need to check whether or not
766 : // the connect was successful. if so, then we have to reset the transaction
767 : // and step-up the socket connection to SSL. finally, we have to wake up the
768 : // socket write request.
769 2819 : if (mProxyConnectStream) {
770 0 : NS_ABORT_IF_FALSE(!mUsingSpdy,
771 : "SPDY NPN Complete while using proxy connect stream");
772 0 : mProxyConnectStream = 0;
773 0 : if (responseHead->Status() == 200) {
774 0 : LOG(("proxy CONNECT succeeded! ssl=%s\n",
775 : mConnInfo->UsingSSL() ? "true" :"false"));
776 0 : *reset = true;
777 : nsresult rv;
778 0 : if (mConnInfo->UsingSSL()) {
779 0 : rv = ProxyStartSSL();
780 0 : if (NS_FAILED(rv)) // XXX need to handle this for real
781 0 : LOG(("ProxyStartSSL failed [rv=%x]\n", rv));
782 : }
783 0 : mCompletedProxyConnect = true;
784 0 : rv = mSocketOut->AsyncWait(this, 0, 0, nsnull);
785 : // XXX what if this fails -- need to handle this error
786 0 : NS_ASSERTION(NS_SUCCEEDED(rv), "mSocketOut->AsyncWait failed");
787 : }
788 : else {
789 0 : LOG(("proxy CONNECT failed! ssl=%s\n",
790 : mConnInfo->UsingSSL() ? "true" :"false"));
791 0 : mTransaction->SetSSLConnectFailed();
792 : }
793 : }
794 :
795 2819 : const char *upgradeReq = requestHead->PeekHeader(nsHttp::Upgrade);
796 2819 : if (upgradeReq) {
797 0 : LOG(("HTTP Upgrade in play - disable keepalive\n"));
798 0 : DontReuse();
799 : }
800 :
801 2819 : if (responseHead->Status() == 101) {
802 0 : const char *upgradeResp = responseHead->PeekHeader(nsHttp::Upgrade);
803 0 : if (!upgradeReq || !upgradeResp ||
804 : !nsHttp::FindToken(upgradeResp, upgradeReq,
805 0 : HTTP_HEADER_VALUE_SEPS)) {
806 0 : LOG(("HTTP 101 Upgrade header mismatch req = %s, resp = %s\n",
807 : upgradeReq, upgradeResp));
808 0 : Close(NS_ERROR_ABORT);
809 : }
810 : else {
811 0 : LOG(("HTTP Upgrade Response to %s\n", upgradeResp));
812 : }
813 : }
814 :
815 2819 : return NS_OK;
816 : }
817 :
818 : bool
819 2975 : nsHttpConnection::IsReused()
820 : {
821 2975 : if (mIsReused)
822 0 : return true;
823 2975 : if (!mConsiderReusedAfterInterval)
824 2975 : return false;
825 :
826 : // ReusedAfter allows a socket to be consider reused only after a certain
827 : // interval of time has passed
828 0 : return (PR_IntervalNow() - mConsiderReusedAfterEpoch) >=
829 0 : mConsiderReusedAfterInterval;
830 : }
831 :
832 : void
833 1 : nsHttpConnection::SetIsReusedAfter(PRUint32 afterMilliseconds)
834 : {
835 1 : mConsiderReusedAfterEpoch = PR_IntervalNow();
836 1 : mConsiderReusedAfterInterval = PR_MillisecondsToInterval(afterMilliseconds);
837 1 : }
838 :
839 : nsresult
840 0 : nsHttpConnection::TakeTransport(nsISocketTransport **aTransport,
841 : nsIAsyncInputStream **aInputStream,
842 : nsIAsyncOutputStream **aOutputStream)
843 : {
844 0 : if (mUsingSpdy)
845 0 : return NS_ERROR_FAILURE;
846 0 : if (mTransaction && !mTransaction->IsDone())
847 0 : return NS_ERROR_IN_PROGRESS;
848 0 : if (!(mSocketTransport && mSocketIn && mSocketOut))
849 0 : return NS_ERROR_NOT_INITIALIZED;
850 :
851 0 : if (mInputOverflow)
852 0 : mSocketIn = mInputOverflow.forget();
853 :
854 0 : NS_IF_ADDREF(*aTransport = mSocketTransport);
855 0 : NS_IF_ADDREF(*aInputStream = mSocketIn);
856 0 : NS_IF_ADDREF(*aOutputStream = mSocketOut);
857 :
858 0 : mSocketTransport->SetSecurityCallbacks(nsnull);
859 0 : mSocketTransport->SetEventSink(nsnull, nsnull);
860 0 : mSocketTransport = nsnull;
861 0 : mSocketIn = nsnull;
862 0 : mSocketOut = nsnull;
863 :
864 0 : return NS_OK;
865 : }
866 :
867 : void
868 0 : nsHttpConnection::ReadTimeoutTick(PRIntervalTime now)
869 : {
870 0 : NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread");
871 :
872 : // make sure timer didn't tick before Activate()
873 0 : if (!mTransaction)
874 0 : return;
875 :
876 : // Spdy in the future actually should implement some timeout handling
877 : // using the SPDY ping frame.
878 0 : if (mSpdySession) {
879 0 : mSpdySession->ReadTimeoutTick(now);
880 0 : return;
881 : }
882 :
883 : // Pending patches places pipeline rescheduling code will go here
884 :
885 : }
886 :
887 : void
888 2975 : nsHttpConnection::GetSecurityInfo(nsISupports **secinfo)
889 : {
890 2975 : NS_ASSERTION(PR_GetCurrentThread() == gSocketThread, "wrong thread");
891 :
892 2975 : if (mSocketTransport) {
893 2975 : if (NS_FAILED(mSocketTransport->GetSecurityInfo(secinfo)))
894 0 : *secinfo = nsnull;
895 : }
896 2975 : }
897 :
898 : nsresult
899 0 : nsHttpConnection::PushBack(const char *data, PRUint32 length)
900 : {
901 0 : LOG(("nsHttpConnection::PushBack [this=%p, length=%d]\n", this, length));
902 :
903 0 : if (mInputOverflow) {
904 0 : NS_ERROR("nsHttpConnection::PushBack only one buffer supported");
905 0 : return NS_ERROR_UNEXPECTED;
906 : }
907 :
908 0 : mInputOverflow = new nsPreloadedStream(mSocketIn, data, length);
909 0 : return NS_OK;
910 : }
911 :
912 : nsresult
913 0 : nsHttpConnection::ResumeSend()
914 : {
915 0 : LOG(("nsHttpConnection::ResumeSend [this=%p]\n", this));
916 :
917 0 : NS_ASSERTION(PR_GetCurrentThread() == gSocketThread, "wrong thread");
918 :
919 0 : if (mSocketOut)
920 0 : return mSocketOut->AsyncWait(this, 0, 0, nsnull);
921 :
922 0 : NS_NOTREACHED("no socket output stream");
923 0 : return NS_ERROR_UNEXPECTED;
924 : }
925 :
926 : nsresult
927 0 : nsHttpConnection::ResumeRecv()
928 : {
929 0 : LOG(("nsHttpConnection::ResumeRecv [this=%p]\n", this));
930 :
931 0 : NS_ASSERTION(PR_GetCurrentThread() == gSocketThread, "wrong thread");
932 :
933 0 : if (mSocketIn)
934 0 : return mSocketIn->AsyncWait(this, 0, 0, nsnull);
935 :
936 0 : NS_NOTREACHED("no socket input stream");
937 0 : return NS_ERROR_UNEXPECTED;
938 : }
939 :
940 : void
941 5 : nsHttpConnection::BeginIdleMonitoring()
942 : {
943 5 : LOG(("nsHttpConnection::BeginIdleMonitoring [this=%p]\n", this));
944 5 : NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread");
945 5 : NS_ABORT_IF_FALSE(!mTransaction, "BeginIdleMonitoring() while active");
946 5 : NS_ABORT_IF_FALSE(!mUsingSpdy, "Idle monitoring of spdy not allowed");
947 :
948 5 : LOG(("Entering Idle Monitoring Mode [this=%p]", this));
949 5 : mIdleMonitoring = true;
950 5 : if (mSocketIn)
951 5 : mSocketIn->AsyncWait(this, 0, 0, nsnull);
952 5 : }
953 :
954 : void
955 5 : nsHttpConnection::EndIdleMonitoring()
956 : {
957 5 : LOG(("nsHttpConnection::EndIdleMonitoring [this=%p]\n", this));
958 5 : NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread");
959 5 : NS_ABORT_IF_FALSE(!mTransaction, "EndIdleMonitoring() while active");
960 :
961 5 : if (mIdleMonitoring) {
962 5 : LOG(("Leaving Idle Monitoring Mode [this=%p]", this));
963 5 : mIdleMonitoring = false;
964 5 : if (mSocketIn)
965 5 : mSocketIn->AsyncWait(nsnull, 0, 0, nsnull);
966 : }
967 5 : }
968 :
969 : //-----------------------------------------------------------------------------
970 : // nsHttpConnection <private>
971 : //-----------------------------------------------------------------------------
972 :
973 : void
974 2975 : nsHttpConnection::CloseTransaction(nsAHttpTransaction *trans, nsresult reason)
975 : {
976 2975 : LOG(("nsHttpConnection::CloseTransaction[this=%x trans=%x reason=%x]\n",
977 : this, trans, reason));
978 :
979 2975 : NS_ASSERTION(trans == mTransaction, "wrong transaction");
980 2975 : NS_ASSERTION(PR_GetCurrentThread() == gSocketThread, "wrong thread");
981 :
982 2975 : if (mCurrentBytesRead > mMaxBytesRead)
983 2499 : mMaxBytesRead = mCurrentBytesRead;
984 :
985 : // mask this error code because its not a real error.
986 2975 : if (reason == NS_BASE_STREAM_CLOSED)
987 2814 : reason = NS_OK;
988 :
989 2975 : if (mUsingSpdy) {
990 0 : DontReuse();
991 : // if !mSpdySession then mUsingSpdy must be false for canreuse()
992 0 : mUsingSpdy = false;
993 0 : mSpdySession = nsnull;
994 : }
995 :
996 2975 : mHttp1xTransactionCount += mTransaction->Http1xTransactionCount();
997 :
998 2975 : mTransaction->Close(reason);
999 2975 : mTransaction = nsnull;
1000 :
1001 2975 : if (mCallbacks) {
1002 2975 : nsIInterfaceRequestor *cbs = nsnull;
1003 2975 : mCallbacks.swap(cbs);
1004 2975 : NS_ProxyRelease(mCallbackTarget, cbs);
1005 : }
1006 :
1007 2975 : if (NS_FAILED(reason))
1008 161 : Close(reason);
1009 :
1010 : // flag the connection as reused here for convenience sake. certainly
1011 : // it might be going away instead ;-)
1012 2975 : mIsReused = true;
1013 2975 : }
1014 :
1015 : NS_METHOD
1016 0 : nsHttpConnection::ReadFromStream(nsIInputStream *input,
1017 : void *closure,
1018 : const char *buf,
1019 : PRUint32 offset,
1020 : PRUint32 count,
1021 : PRUint32 *countRead)
1022 : {
1023 : // thunk for nsIInputStream instance
1024 0 : nsHttpConnection *conn = (nsHttpConnection *) closure;
1025 0 : return conn->OnReadSegment(buf, count, countRead);
1026 : }
1027 :
1028 : nsresult
1029 2978 : nsHttpConnection::OnReadSegment(const char *buf,
1030 : PRUint32 count,
1031 : PRUint32 *countRead)
1032 : {
1033 2978 : if (count == 0) {
1034 : // some ReadSegments implementations will erroneously call the writer
1035 : // to consume 0 bytes worth of data. we must protect against this case
1036 : // or else we'd end up closing the socket prematurely.
1037 0 : NS_ERROR("bad ReadSegments implementation");
1038 0 : return NS_ERROR_FAILURE; // stop iterating
1039 : }
1040 :
1041 2978 : nsresult rv = mSocketOut->Write(buf, count, countRead);
1042 2978 : if (NS_FAILED(rv))
1043 142 : mSocketOutCondition = rv;
1044 2836 : else if (*countRead == 0)
1045 0 : mSocketOutCondition = NS_BASE_STREAM_CLOSED;
1046 : else
1047 2836 : mSocketOutCondition = NS_OK; // reset condition
1048 :
1049 2978 : return mSocketOutCondition;
1050 : }
1051 :
1052 : nsresult
1053 2988 : nsHttpConnection::OnSocketWritable()
1054 : {
1055 2988 : LOG(("nsHttpConnection::OnSocketWritable [this=%x]\n", this));
1056 :
1057 : nsresult rv;
1058 : PRUint32 n;
1059 2988 : bool again = true;
1060 :
1061 5824 : do {
1062 5824 : mSocketOutCondition = NS_OK;
1063 :
1064 : // if we're doing an SSL proxy connect, then we need to bypass calling
1065 : // into the transaction.
1066 : //
1067 : // NOTE: this code path can't be shared since the transaction doesn't
1068 : // implement nsIInputStream. doing so is not worth the added cost of
1069 : // extra indirections during normal reading.
1070 : //
1071 5824 : if (mProxyConnectStream) {
1072 0 : LOG((" writing CONNECT request stream\n"));
1073 0 : rv = mProxyConnectStream->ReadSegments(ReadFromStream, this,
1074 : nsIOService::gDefaultSegmentSize,
1075 0 : &n);
1076 : }
1077 5824 : else if (!EnsureNPNComplete()) {
1078 : // When SPDY is disabled this branch is not executed because Activate()
1079 : // sets mNPNComplete to true in that case.
1080 :
1081 : // We are ready to proceed with SSL but the handshake is not done.
1082 : // When using NPN to negotiate between HTTPS and SPDY, we need to
1083 : // see the results of the handshake to know what bytes to send, so
1084 : // we cannot proceed with the request headers.
1085 :
1086 12 : rv = NS_OK;
1087 12 : mSocketOutCondition = NS_BASE_STREAM_WOULD_BLOCK;
1088 12 : n = 0;
1089 : }
1090 : else {
1091 5812 : if (!mReportedSpdy) {
1092 2975 : mReportedSpdy = true;
1093 2975 : gHttpHandler->ConnMgr()->ReportSpdyConnection(this, mUsingSpdy);
1094 : }
1095 :
1096 5812 : LOG((" writing transaction request stream\n"));
1097 5812 : rv = mTransaction->ReadSegments(this, nsIOService::gDefaultSegmentSize, &n);
1098 : }
1099 :
1100 5824 : LOG((" ReadSegments returned [rv=%x read=%u sock-cond=%x]\n",
1101 : rv, n, mSocketOutCondition));
1102 :
1103 : // XXX some streams return NS_BASE_STREAM_CLOSED to indicate EOF.
1104 5824 : if (rv == NS_BASE_STREAM_CLOSED) {
1105 0 : rv = NS_OK;
1106 0 : n = 0;
1107 : }
1108 :
1109 5824 : if (NS_FAILED(rv)) {
1110 : // if the transaction didn't want to write any more data, then
1111 : // wait for the transaction to call ResumeSend.
1112 0 : if (rv == NS_BASE_STREAM_WOULD_BLOCK)
1113 0 : rv = NS_OK;
1114 0 : again = false;
1115 : }
1116 5824 : else if (NS_FAILED(mSocketOutCondition)) {
1117 154 : if (mSocketOutCondition == NS_BASE_STREAM_WOULD_BLOCK)
1118 13 : rv = mSocketOut->AsyncWait(this, 0, 0, nsnull); // continue writing
1119 : else
1120 141 : rv = mSocketOutCondition;
1121 154 : again = false;
1122 : }
1123 5670 : else if (n == 0) {
1124 : //
1125 : // at this point we've written out the entire transaction, and now we
1126 : // must wait for the server's response. we manufacture a status message
1127 : // here to reflect the fact that we are waiting. this message will be
1128 : // trumped (overwritten) if the server responds quickly.
1129 : //
1130 2834 : mTransaction->OnTransportStatus(mSocketTransport,
1131 : nsISocketTransport::STATUS_WAITING_FOR,
1132 2834 : LL_ZERO);
1133 :
1134 2834 : rv = mSocketIn->AsyncWait(this, 0, 0, nsnull); // start reading
1135 2834 : again = false;
1136 : }
1137 : // write more to the socket until error or end-of-request...
1138 : } while (again);
1139 :
1140 2988 : return rv;
1141 : }
1142 :
1143 : nsresult
1144 8640 : nsHttpConnection::OnWriteSegment(char *buf,
1145 : PRUint32 count,
1146 : PRUint32 *countWritten)
1147 : {
1148 8640 : if (count == 0) {
1149 : // some WriteSegments implementations will erroneously call the reader
1150 : // to provide 0 bytes worth of data. we must protect against this case
1151 : // or else we'd end up closing the socket prematurely.
1152 0 : NS_ERROR("bad WriteSegments implementation");
1153 0 : return NS_ERROR_FAILURE; // stop iterating
1154 : }
1155 :
1156 8640 : nsresult rv = mSocketIn->Read(buf, count, countWritten);
1157 8640 : if (NS_FAILED(rv))
1158 2905 : mSocketInCondition = rv;
1159 5735 : else if (*countWritten == 0)
1160 14 : mSocketInCondition = NS_BASE_STREAM_CLOSED;
1161 : else
1162 5721 : mSocketInCondition = NS_OK; // reset condition
1163 :
1164 8640 : return mSocketInCondition;
1165 : }
1166 :
1167 : nsresult
1168 5730 : nsHttpConnection::OnSocketReadable()
1169 : {
1170 5730 : LOG(("nsHttpConnection::OnSocketReadable [this=%x]\n", this));
1171 :
1172 5730 : PRIntervalTime now = PR_IntervalNow();
1173 :
1174 5730 : if (mKeepAliveMask && ((now - mLastReadTime) >= mMaxHangTime)) {
1175 0 : LOG(("max hang time exceeded!\n"));
1176 : // give the handler a chance to create a new persistent connection to
1177 : // this host if we've been busy for too long.
1178 0 : mKeepAliveMask = false;
1179 0 : gHttpHandler->ProcessPendingQ(mConnInfo);
1180 : }
1181 5730 : mLastReadTime = now;
1182 :
1183 : nsresult rv;
1184 : PRUint32 n;
1185 5730 : bool again = true;
1186 :
1187 11025 : do {
1188 11025 : rv = mTransaction->WriteSegments(this, nsIOService::gDefaultSegmentSize, &n);
1189 11025 : if (NS_FAILED(rv)) {
1190 : // if the transaction didn't want to take any more data, then
1191 : // wait for the transaction to call ResumeRecv.
1192 2811 : if (rv == NS_BASE_STREAM_WOULD_BLOCK)
1193 0 : rv = NS_OK;
1194 2811 : again = false;
1195 : }
1196 : else {
1197 8214 : mCurrentBytesRead += n;
1198 8214 : mTotalBytesRead += n;
1199 8214 : if (NS_FAILED(mSocketInCondition)) {
1200 : // continue waiting for the socket if necessary...
1201 2919 : if (mSocketInCondition == NS_BASE_STREAM_WOULD_BLOCK)
1202 2905 : rv = mSocketIn->AsyncWait(this, 0, 0, nsnull);
1203 : else
1204 14 : rv = mSocketInCondition;
1205 2919 : again = false;
1206 : }
1207 : }
1208 : // read more from the socket until error...
1209 : } while (again);
1210 :
1211 5730 : return rv;
1212 : }
1213 :
1214 : nsresult
1215 0 : nsHttpConnection::SetupProxyConnect()
1216 : {
1217 : const char *val;
1218 :
1219 0 : LOG(("nsHttpConnection::SetupProxyConnect [this=%x]\n", this));
1220 :
1221 0 : NS_ENSURE_TRUE(!mProxyConnectStream, NS_ERROR_ALREADY_INITIALIZED);
1222 0 : NS_ABORT_IF_FALSE(!mUsingSpdy,
1223 : "SPDY NPN Complete while using proxy connect stream");
1224 :
1225 0 : nsCAutoString buf;
1226 : nsresult rv = nsHttpHandler::GenerateHostPort(
1227 0 : nsDependentCString(mConnInfo->Host()), mConnInfo->Port(), buf);
1228 0 : if (NS_FAILED(rv))
1229 0 : return rv;
1230 :
1231 : // CONNECT host:port HTTP/1.1
1232 0 : nsHttpRequestHead request;
1233 0 : request.SetMethod(nsHttp::Connect);
1234 0 : request.SetVersion(gHttpHandler->HttpVersion());
1235 0 : request.SetRequestURI(buf);
1236 0 : request.SetHeader(nsHttp::User_Agent, gHttpHandler->UserAgent());
1237 :
1238 : // send this header for backwards compatibility.
1239 0 : request.SetHeader(nsHttp::Proxy_Connection, NS_LITERAL_CSTRING("keep-alive"));
1240 :
1241 0 : val = mTransaction->RequestHead()->PeekHeader(nsHttp::Host);
1242 0 : if (val) {
1243 : // all HTTP/1.1 requests must include a Host header (even though it
1244 : // may seem redundant in this case; see bug 82388).
1245 0 : request.SetHeader(nsHttp::Host, nsDependentCString(val));
1246 : }
1247 :
1248 0 : val = mTransaction->RequestHead()->PeekHeader(nsHttp::Proxy_Authorization);
1249 0 : if (val) {
1250 : // we don't know for sure if this authorization is intended for the
1251 : // SSL proxy, so we add it just in case.
1252 0 : request.SetHeader(nsHttp::Proxy_Authorization, nsDependentCString(val));
1253 : }
1254 :
1255 0 : buf.Truncate();
1256 0 : request.Flatten(buf, false);
1257 0 : buf.AppendLiteral("\r\n");
1258 :
1259 0 : return NS_NewCStringInputStream(getter_AddRefs(mProxyConnectStream), buf);
1260 : }
1261 :
1262 : //-----------------------------------------------------------------------------
1263 : // nsHttpConnection::nsISupports
1264 : //-----------------------------------------------------------------------------
1265 :
1266 111041 : NS_IMPL_THREADSAFE_ISUPPORTS4(nsHttpConnection,
1267 : nsIInputStreamCallback,
1268 : nsIOutputStreamCallback,
1269 : nsITransportEventSink,
1270 : nsIInterfaceRequestor)
1271 :
1272 : //-----------------------------------------------------------------------------
1273 : // nsHttpConnection::nsIInputStreamCallback
1274 : //-----------------------------------------------------------------------------
1275 :
1276 : // called on the socket transport thread
1277 : NS_IMETHODIMP
1278 5739 : nsHttpConnection::OnInputStreamReady(nsIAsyncInputStream *in)
1279 : {
1280 5739 : NS_ASSERTION(in == mSocketIn, "unexpected stream");
1281 5739 : NS_ASSERTION(PR_GetCurrentThread() == gSocketThread, "wrong thread");
1282 :
1283 5739 : if (mIdleMonitoring) {
1284 0 : NS_ABORT_IF_FALSE(!mTransaction, "Idle Input Event While Active");
1285 :
1286 : // The only read event that is protocol compliant for an idle connection
1287 : // is an EOF, which we check for with CanReuse(). If the data is
1288 : // something else then just ignore it and suspend checking for EOF -
1289 : // our normal timers or protocol stack are the place to deal with
1290 : // any exception logic.
1291 :
1292 0 : if (!CanReuse()) {
1293 0 : LOG(("Server initiated close of idle conn %p\n", this));
1294 0 : gHttpHandler->ConnMgr()->CloseIdleConnection(this);
1295 0 : return NS_OK;
1296 : }
1297 :
1298 0 : LOG(("Input data on idle conn %p, but not closing yet\n", this));
1299 0 : return NS_OK;
1300 : }
1301 :
1302 : // if the transaction was dropped...
1303 5739 : if (!mTransaction) {
1304 9 : LOG((" no transaction; ignoring event\n"));
1305 9 : return NS_OK;
1306 : }
1307 :
1308 5730 : nsresult rv = OnSocketReadable();
1309 5730 : if (NS_FAILED(rv))
1310 2825 : CloseTransaction(mTransaction, rv);
1311 :
1312 5730 : return NS_OK;
1313 : }
1314 :
1315 : //-----------------------------------------------------------------------------
1316 : // nsHttpConnection::nsIOutputStreamCallback
1317 : //-----------------------------------------------------------------------------
1318 :
1319 : NS_IMETHODIMP
1320 2988 : nsHttpConnection::OnOutputStreamReady(nsIAsyncOutputStream *out)
1321 : {
1322 2988 : NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread");
1323 2988 : NS_ABORT_IF_FALSE(out == mSocketOut, "unexpected socket");
1324 :
1325 : // if the transaction was dropped...
1326 2988 : if (!mTransaction) {
1327 0 : LOG((" no transaction; ignoring event\n"));
1328 0 : return NS_OK;
1329 : }
1330 :
1331 2988 : nsresult rv = OnSocketWritable();
1332 2988 : if (NS_FAILED(rv))
1333 141 : CloseTransaction(mTransaction, rv);
1334 :
1335 2988 : return NS_OK;
1336 : }
1337 :
1338 : //-----------------------------------------------------------------------------
1339 : // nsHttpConnection::nsITransportEventSink
1340 : //-----------------------------------------------------------------------------
1341 :
1342 : NS_IMETHODIMP
1343 8557 : nsHttpConnection::OnTransportStatus(nsITransport *trans,
1344 : nsresult status,
1345 : PRUint64 progress,
1346 : PRUint64 progressMax)
1347 : {
1348 8557 : if (mTransaction)
1349 8557 : mTransaction->OnTransportStatus(trans, status, progress);
1350 8557 : return NS_OK;
1351 : }
1352 :
1353 : //-----------------------------------------------------------------------------
1354 : // nsHttpConnection::nsIInterfaceRequestor
1355 : //-----------------------------------------------------------------------------
1356 :
1357 : // not called on the socket transport thread
1358 : NS_IMETHODIMP
1359 8 : nsHttpConnection::GetInterface(const nsIID &iid, void **result)
1360 : {
1361 : // NOTE: This function is only called on the UI thread via sync proxy from
1362 : // the socket transport thread. If that weren't the case, then we'd
1363 : // have to worry about the possibility of mTransaction going away
1364 : // part-way through this function call. See CloseTransaction.
1365 :
1366 : // NOTE - there is a bug here, the call to getinterface is proxied off the
1367 : // nss thread, not the ui thread as the above comment says. So there is
1368 : // indeed a chance of mTransaction going away. bug 615342
1369 :
1370 8 : NS_ASSERTION(PR_GetCurrentThread() != gSocketThread, "wrong thread");
1371 :
1372 8 : if (mCallbacks)
1373 8 : return mCallbacks->GetInterface(iid, result);
1374 0 : return NS_ERROR_NO_INTERFACE;
1375 : }
1376 :
|