netwerk/protocol/http/nsHttpConnection.cpp

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

michael@0 1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
michael@0 2 /* vim:set ts=4 sw=4 sts=4 et cin: */
michael@0 3 /* This Source Code Form is subject to the terms of the Mozilla Public
michael@0 4 * License, v. 2.0. If a copy of the MPL was not distributed with this
michael@0 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0 6
michael@0 7 // HttpLog.h should generally be included first
michael@0 8 #include "HttpLog.h"
michael@0 9
michael@0 10 // Log on level :5, instead of default :4.
michael@0 11 #undef LOG
michael@0 12 #define LOG(args) LOG5(args)
michael@0 13 #undef LOG_ENABLED
michael@0 14 #define LOG_ENABLED() LOG5_ENABLED()
michael@0 15
michael@0 16 #include "nsHttpConnection.h"
michael@0 17 #include "nsHttpRequestHead.h"
michael@0 18 #include "nsHttpResponseHead.h"
michael@0 19 #include "nsHttpHandler.h"
michael@0 20 #include "nsIOService.h"
michael@0 21 #include "nsISocketTransport.h"
michael@0 22 #include "nsSocketTransportService2.h"
michael@0 23 #include "nsISSLSocketControl.h"
michael@0 24 #include "sslt.h"
michael@0 25 #include "nsStringStream.h"
michael@0 26 #include "nsProxyRelease.h"
michael@0 27 #include "nsPreloadedStream.h"
michael@0 28 #include "ASpdySession.h"
michael@0 29 #include "mozilla/Telemetry.h"
michael@0 30 #include "nsISupportsPriority.h"
michael@0 31 #include "nsHttpPipeline.h"
michael@0 32 #include <algorithm>
michael@0 33 #include "mozilla/ChaosMode.h"
michael@0 34
michael@0 35 #ifdef DEBUG
michael@0 36 // defined by the socket transport service while active
michael@0 37 extern PRThread *gSocketThread;
michael@0 38 #endif
michael@0 39
michael@0 40 namespace mozilla {
michael@0 41 namespace net {
michael@0 42
michael@0 43 //-----------------------------------------------------------------------------
michael@0 44 // nsHttpConnection <public>
michael@0 45 //-----------------------------------------------------------------------------
michael@0 46
michael@0 47 nsHttpConnection::nsHttpConnection()
michael@0 48 : mTransaction(nullptr)
michael@0 49 , mHttpHandler(gHttpHandler)
michael@0 50 , mCallbacksLock("nsHttpConnection::mCallbacksLock")
michael@0 51 , mConsiderReusedAfterInterval(0)
michael@0 52 , mConsiderReusedAfterEpoch(0)
michael@0 53 , mCurrentBytesRead(0)
michael@0 54 , mMaxBytesRead(0)
michael@0 55 , mTotalBytesRead(0)
michael@0 56 , mTotalBytesWritten(0)
michael@0 57 , mKeepAlive(true) // assume to keep-alive by default
michael@0 58 , mKeepAliveMask(true)
michael@0 59 , mDontReuse(false)
michael@0 60 , mSupportsPipelining(false) // assume low-grade server
michael@0 61 , mIsReused(false)
michael@0 62 , mCompletedProxyConnect(false)
michael@0 63 , mLastTransactionExpectedNoContent(false)
michael@0 64 , mIdleMonitoring(false)
michael@0 65 , mProxyConnectInProgress(false)
michael@0 66 , mExperienced(false)
michael@0 67 , mHttp1xTransactionCount(0)
michael@0 68 , mRemainingConnectionUses(0xffffffff)
michael@0 69 , mClassification(nsAHttpTransaction::CLASS_GENERAL)
michael@0 70 , mNPNComplete(false)
michael@0 71 , mSetupSSLCalled(false)
michael@0 72 , mUsingSpdyVersion(0)
michael@0 73 , mPriority(nsISupportsPriority::PRIORITY_NORMAL)
michael@0 74 , mReportedSpdy(false)
michael@0 75 , mEverUsedSpdy(false)
michael@0 76 , mLastHttpResponseVersion(NS_HTTP_VERSION_1_1)
michael@0 77 , mTransactionCaps(0)
michael@0 78 , mResponseTimeoutEnabled(false)
michael@0 79 , mTCPKeepaliveConfig(kTCPKeepaliveDisabled)
michael@0 80 {
michael@0 81 LOG(("Creating nsHttpConnection @%x\n", this));
michael@0 82
michael@0 83 // the default timeout is for when this connection has not yet processed a
michael@0 84 // transaction
michael@0 85 static const PRIntervalTime k5Sec = PR_SecondsToInterval(5);
michael@0 86 mIdleTimeout =
michael@0 87 (k5Sec < gHttpHandler->IdleTimeout()) ? k5Sec : gHttpHandler->IdleTimeout();
michael@0 88 }
michael@0 89
michael@0 90 nsHttpConnection::~nsHttpConnection()
michael@0 91 {
michael@0 92 LOG(("Destroying nsHttpConnection @%x\n", this));
michael@0 93
michael@0 94 if (!mEverUsedSpdy) {
michael@0 95 LOG(("nsHttpConnection %p performed %d HTTP/1.x transactions\n",
michael@0 96 this, mHttp1xTransactionCount));
michael@0 97 Telemetry::Accumulate(Telemetry::HTTP_REQUEST_PER_CONN,
michael@0 98 mHttp1xTransactionCount);
michael@0 99 }
michael@0 100
michael@0 101 if (mTotalBytesRead) {
michael@0 102 uint32_t totalKBRead = static_cast<uint32_t>(mTotalBytesRead >> 10);
michael@0 103 LOG(("nsHttpConnection %p read %dkb on connection spdy=%d\n",
michael@0 104 this, totalKBRead, mEverUsedSpdy));
michael@0 105 Telemetry::Accumulate(mEverUsedSpdy ?
michael@0 106 Telemetry::SPDY_KBREAD_PER_CONN :
michael@0 107 Telemetry::HTTP_KBREAD_PER_CONN,
michael@0 108 totalKBRead);
michael@0 109 }
michael@0 110 }
michael@0 111
michael@0 112 nsresult
michael@0 113 nsHttpConnection::Init(nsHttpConnectionInfo *info,
michael@0 114 uint16_t maxHangTime,
michael@0 115 nsISocketTransport *transport,
michael@0 116 nsIAsyncInputStream *instream,
michael@0 117 nsIAsyncOutputStream *outstream,
michael@0 118 nsIInterfaceRequestor *callbacks,
michael@0 119 PRIntervalTime rtt)
michael@0 120 {
michael@0 121 MOZ_ASSERT(transport && instream && outstream,
michael@0 122 "invalid socket information");
michael@0 123 LOG(("nsHttpConnection::Init [this=%p "
michael@0 124 "transport=%p instream=%p outstream=%p rtt=%d]\n",
michael@0 125 this, transport, instream, outstream,
michael@0 126 PR_IntervalToMilliseconds(rtt)));
michael@0 127
michael@0 128 NS_ENSURE_ARG_POINTER(info);
michael@0 129 NS_ENSURE_TRUE(!mConnInfo, NS_ERROR_ALREADY_INITIALIZED);
michael@0 130
michael@0 131 mConnInfo = info;
michael@0 132 mLastWriteTime = mLastReadTime = PR_IntervalNow();
michael@0 133 mSupportsPipelining =
michael@0 134 gHttpHandler->ConnMgr()->SupportsPipelining(mConnInfo);
michael@0 135 mRtt = rtt;
michael@0 136 mMaxHangTime = PR_SecondsToInterval(maxHangTime);
michael@0 137
michael@0 138 mSocketTransport = transport;
michael@0 139 mSocketIn = instream;
michael@0 140 mSocketOut = outstream;
michael@0 141 nsresult rv = mSocketTransport->SetEventSink(this, nullptr);
michael@0 142 NS_ENSURE_SUCCESS(rv, rv);
michael@0 143
michael@0 144 // See explanation for non-strictness of this operation in SetSecurityCallbacks.
michael@0 145 mCallbacks = new nsMainThreadPtrHolder<nsIInterfaceRequestor>(callbacks, false);
michael@0 146 rv = mSocketTransport->SetSecurityCallbacks(this);
michael@0 147 NS_ENSURE_SUCCESS(rv, rv);
michael@0 148
michael@0 149 return NS_OK;
michael@0 150 }
michael@0 151
michael@0 152 void
michael@0 153 nsHttpConnection::StartSpdy(uint8_t spdyVersion)
michael@0 154 {
michael@0 155 LOG(("nsHttpConnection::StartSpdy [this=%p]\n", this));
michael@0 156
michael@0 157 MOZ_ASSERT(!mSpdySession);
michael@0 158
michael@0 159 mUsingSpdyVersion = spdyVersion;
michael@0 160 mEverUsedSpdy = true;
michael@0 161
michael@0 162 // Setting the connection as reused allows some transactions that fail
michael@0 163 // with NS_ERROR_NET_RESET to be restarted and SPDY uses that code
michael@0 164 // to handle clean rejections (such as those that arrived after
michael@0 165 // a server goaway was generated).
michael@0 166 mIsReused = true;
michael@0 167
michael@0 168 // If mTransaction is a pipeline object it might represent
michael@0 169 // several requests. If so, we need to unpack that and
michael@0 170 // pack them all into a new spdy session.
michael@0 171
michael@0 172 nsTArray<nsRefPtr<nsAHttpTransaction> > list;
michael@0 173 nsresult rv = mTransaction->TakeSubTransactions(list);
michael@0 174
michael@0 175 if (rv == NS_ERROR_ALREADY_OPENED) {
michael@0 176 // Has the interface for TakeSubTransactions() changed?
michael@0 177 LOG(("TakeSubTranscations somehow called after "
michael@0 178 "nsAHttpTransaction began processing\n"));
michael@0 179 MOZ_ASSERT(false,
michael@0 180 "TakeSubTranscations somehow called after "
michael@0 181 "nsAHttpTransaction began processing");
michael@0 182 mTransaction->Close(NS_ERROR_ABORT);
michael@0 183 return;
michael@0 184 }
michael@0 185
michael@0 186 if (NS_FAILED(rv) && rv != NS_ERROR_NOT_IMPLEMENTED) {
michael@0 187 // Has the interface for TakeSubTransactions() changed?
michael@0 188 LOG(("unexpected rv from nnsAHttpTransaction::TakeSubTransactions()"));
michael@0 189 MOZ_ASSERT(false,
michael@0 190 "unexpected result from "
michael@0 191 "nsAHttpTransaction::TakeSubTransactions()");
michael@0 192 mTransaction->Close(NS_ERROR_ABORT);
michael@0 193 return;
michael@0 194 }
michael@0 195
michael@0 196 if (NS_FAILED(rv)) { // includes NS_ERROR_NOT_IMPLEMENTED
michael@0 197 MOZ_ASSERT(list.IsEmpty(), "sub transaction list not empty");
michael@0 198
michael@0 199 // This is ok - treat mTransaction as a single real request.
michael@0 200 // Wrap the old http transaction into the new spdy session
michael@0 201 // as the first stream.
michael@0 202 mSpdySession = ASpdySession::NewSpdySession(spdyVersion,
michael@0 203 mTransaction, mSocketTransport,
michael@0 204 mPriority);
michael@0 205 LOG(("nsHttpConnection::StartSpdy moves single transaction %p "
michael@0 206 "into SpdySession %p\n", mTransaction.get(), mSpdySession.get()));
michael@0 207 }
michael@0 208 else {
michael@0 209 int32_t count = list.Length();
michael@0 210
michael@0 211 LOG(("nsHttpConnection::StartSpdy moving transaction list len=%d "
michael@0 212 "into SpdySession %p\n", count, mSpdySession.get()));
michael@0 213
michael@0 214 if (!count) {
michael@0 215 mTransaction->Close(NS_ERROR_ABORT);
michael@0 216 return;
michael@0 217 }
michael@0 218
michael@0 219 for (int32_t index = 0; index < count; ++index) {
michael@0 220 if (!mSpdySession) {
michael@0 221 mSpdySession = ASpdySession::NewSpdySession(spdyVersion,
michael@0 222 list[index], mSocketTransport,
michael@0 223 mPriority);
michael@0 224 }
michael@0 225 else {
michael@0 226 // AddStream() cannot fail
michael@0 227 if (!mSpdySession->AddStream(list[index], mPriority)) {
michael@0 228 MOZ_ASSERT(false, "SpdySession::AddStream failed");
michael@0 229 LOG(("SpdySession::AddStream failed\n"));
michael@0 230 mTransaction->Close(NS_ERROR_ABORT);
michael@0 231 return;
michael@0 232 }
michael@0 233 }
michael@0 234 }
michael@0 235 }
michael@0 236
michael@0 237 // Disable TCP Keepalives - use SPDY ping instead.
michael@0 238 rv = DisableTCPKeepalives();
michael@0 239 if (NS_WARN_IF(NS_FAILED(rv))) {
michael@0 240 LOG(("nsHttpConnection::StartSpdy [%p] DisableTCPKeepalives failed "
michael@0 241 "rv[0x%x]", this, rv));
michael@0 242 }
michael@0 243
michael@0 244 mSupportsPipelining = false; // dont use http/1 pipelines with spdy
michael@0 245 mTransaction = mSpdySession;
michael@0 246 mIdleTimeout = gHttpHandler->SpdyTimeout();
michael@0 247 }
michael@0 248
michael@0 249 bool
michael@0 250 nsHttpConnection::EnsureNPNComplete()
michael@0 251 {
michael@0 252 // If for some reason the components to check on NPN aren't available,
michael@0 253 // this function will just return true to continue on and disable SPDY
michael@0 254
michael@0 255 MOZ_ASSERT(mSocketTransport);
michael@0 256 if (!mSocketTransport) {
michael@0 257 // this cannot happen
michael@0 258 mNPNComplete = true;
michael@0 259 return true;
michael@0 260 }
michael@0 261
michael@0 262 if (mNPNComplete)
michael@0 263 return true;
michael@0 264
michael@0 265 nsresult rv;
michael@0 266
michael@0 267 nsCOMPtr<nsISupports> securityInfo;
michael@0 268 nsCOMPtr<nsISSLSocketControl> ssl;
michael@0 269 nsAutoCString negotiatedNPN;
michael@0 270
michael@0 271 rv = mSocketTransport->GetSecurityInfo(getter_AddRefs(securityInfo));
michael@0 272 if (NS_FAILED(rv))
michael@0 273 goto npnComplete;
michael@0 274
michael@0 275 ssl = do_QueryInterface(securityInfo, &rv);
michael@0 276 if (NS_FAILED(rv))
michael@0 277 goto npnComplete;
michael@0 278
michael@0 279 rv = ssl->GetNegotiatedNPN(negotiatedNPN);
michael@0 280 if (rv == NS_ERROR_NOT_CONNECTED) {
michael@0 281
michael@0 282 // By writing 0 bytes to the socket the SSL handshake machine is
michael@0 283 // pushed forward.
michael@0 284 uint32_t count = 0;
michael@0 285 rv = mSocketOut->Write("", 0, &count);
michael@0 286
michael@0 287 if (NS_FAILED(rv) && rv != NS_BASE_STREAM_WOULD_BLOCK)
michael@0 288 goto npnComplete;
michael@0 289 return false;
michael@0 290 }
michael@0 291
michael@0 292 if (NS_FAILED(rv))
michael@0 293 goto npnComplete;
michael@0 294
michael@0 295 LOG(("nsHttpConnection::EnsureNPNComplete %p [%s] negotiated to '%s'\n",
michael@0 296 this, mConnInfo->Host(), negotiatedNPN.get()));
michael@0 297
michael@0 298 uint8_t spdyVersion;
michael@0 299 rv = gHttpHandler->SpdyInfo()->GetNPNVersionIndex(negotiatedNPN,
michael@0 300 &spdyVersion);
michael@0 301 if (NS_SUCCEEDED(rv))
michael@0 302 StartSpdy(spdyVersion);
michael@0 303
michael@0 304 Telemetry::Accumulate(Telemetry::SPDY_NPN_CONNECT, UsingSpdy());
michael@0 305
michael@0 306 npnComplete:
michael@0 307 LOG(("nsHttpConnection::EnsureNPNComplete setting complete to true"));
michael@0 308 mNPNComplete = true;
michael@0 309 return true;
michael@0 310 }
michael@0 311
michael@0 312 // called on the socket thread
michael@0 313 nsresult
michael@0 314 nsHttpConnection::Activate(nsAHttpTransaction *trans, uint32_t caps, int32_t pri)
michael@0 315 {
michael@0 316 MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
michael@0 317 LOG(("nsHttpConnection::Activate [this=%p trans=%x caps=%x]\n",
michael@0 318 this, trans, caps));
michael@0 319
michael@0 320 if (!trans->IsNullTransaction())
michael@0 321 mExperienced = true;
michael@0 322
michael@0 323 mTransactionCaps = caps;
michael@0 324 mPriority = pri;
michael@0 325 if (mTransaction && mUsingSpdyVersion)
michael@0 326 return AddTransaction(trans, pri);
michael@0 327
michael@0 328 NS_ENSURE_ARG_POINTER(trans);
michael@0 329 NS_ENSURE_TRUE(!mTransaction, NS_ERROR_IN_PROGRESS);
michael@0 330
michael@0 331 // reset the read timers to wash away any idle time
michael@0 332 mLastWriteTime = mLastReadTime = PR_IntervalNow();
michael@0 333
michael@0 334 // Update security callbacks
michael@0 335 nsCOMPtr<nsIInterfaceRequestor> callbacks;
michael@0 336 trans->GetSecurityCallbacks(getter_AddRefs(callbacks));
michael@0 337 SetSecurityCallbacks(callbacks);
michael@0 338
michael@0 339 SetupSSL(caps);
michael@0 340
michael@0 341 // take ownership of the transaction
michael@0 342 mTransaction = trans;
michael@0 343
michael@0 344 MOZ_ASSERT(!mIdleMonitoring, "Activating a connection with an Idle Monitor");
michael@0 345 mIdleMonitoring = false;
michael@0 346
michael@0 347 // set mKeepAlive according to what will be requested
michael@0 348 mKeepAliveMask = mKeepAlive = (caps & NS_HTTP_ALLOW_KEEPALIVE);
michael@0 349
michael@0 350 // need to handle HTTP CONNECT tunnels if this is the first time if
michael@0 351 // we are tunneling through a proxy
michael@0 352 nsresult rv = NS_OK;
michael@0 353 if (mConnInfo->UsingConnect() && !mCompletedProxyConnect) {
michael@0 354 rv = SetupProxyConnect();
michael@0 355 if (NS_FAILED(rv))
michael@0 356 goto failed_activation;
michael@0 357 mProxyConnectInProgress = true;
michael@0 358 }
michael@0 359
michael@0 360 // Clear the per activation counter
michael@0 361 mCurrentBytesRead = 0;
michael@0 362
michael@0 363 // The overflow state is not needed between activations
michael@0 364 mInputOverflow = nullptr;
michael@0 365
michael@0 366 mResponseTimeoutEnabled = gHttpHandler->ResponseTimeoutEnabled() &&
michael@0 367 mTransaction->ResponseTimeout() > 0 &&
michael@0 368 mTransaction->ResponseTimeoutEnabled();
michael@0 369
michael@0 370 rv = StartShortLivedTCPKeepalives();
michael@0 371 if (NS_WARN_IF(NS_FAILED(rv))) {
michael@0 372 LOG(("nsHttpConnection::Activate [%p] "
michael@0 373 "StartShortLivedTCPKeepalives failed rv[0x%x]",
michael@0 374 this, rv));
michael@0 375 }
michael@0 376
michael@0 377 rv = OnOutputStreamReady(mSocketOut);
michael@0 378
michael@0 379 failed_activation:
michael@0 380 if (NS_FAILED(rv)) {
michael@0 381 mTransaction = nullptr;
michael@0 382 }
michael@0 383
michael@0 384 return rv;
michael@0 385 }
michael@0 386
michael@0 387 void
michael@0 388 nsHttpConnection::SetupSSL(uint32_t caps)
michael@0 389 {
michael@0 390 LOG(("nsHttpConnection::SetupSSL %p caps=0x%X\n", this, caps));
michael@0 391
michael@0 392 if (mSetupSSLCalled) // do only once
michael@0 393 return;
michael@0 394 mSetupSSLCalled = true;
michael@0 395
michael@0 396 if (mNPNComplete)
michael@0 397 return;
michael@0 398
michael@0 399 // we flip this back to false if SetNPNList succeeds at the end
michael@0 400 // of this function
michael@0 401 mNPNComplete = true;
michael@0 402
michael@0 403 if (!mConnInfo->UsingSSL())
michael@0 404 return;
michael@0 405
michael@0 406 LOG(("nsHttpConnection::SetupSSL Setting up "
michael@0 407 "Next Protocol Negotiation"));
michael@0 408 nsCOMPtr<nsISupports> securityInfo;
michael@0 409 nsresult rv =
michael@0 410 mSocketTransport->GetSecurityInfo(getter_AddRefs(securityInfo));
michael@0 411 if (NS_FAILED(rv))
michael@0 412 return;
michael@0 413
michael@0 414 nsCOMPtr<nsISSLSocketControl> ssl = do_QueryInterface(securityInfo, &rv);
michael@0 415 if (NS_FAILED(rv))
michael@0 416 return;
michael@0 417
michael@0 418 if (caps & NS_HTTP_ALLOW_RSA_FALSESTART) {
michael@0 419 LOG(("nsHttpConnection::SetupSSL %p "
michael@0 420 ">= RSA Key Exchange Expected\n", this));
michael@0 421 ssl->SetKEAExpected(ssl_kea_rsa);
michael@0 422 }
michael@0 423
michael@0 424 nsTArray<nsCString> protocolArray;
michael@0 425
michael@0 426 // The first protocol is used as the fallback if none of the
michael@0 427 // protocols supported overlap with the server's list.
michael@0 428 // In the case of overlap, matching priority is driven by
michael@0 429 // the order of the server's advertisement.
michael@0 430 protocolArray.AppendElement(NS_LITERAL_CSTRING("http/1.1"));
michael@0 431
michael@0 432 if (gHttpHandler->IsSpdyEnabled() &&
michael@0 433 !(caps & NS_HTTP_DISALLOW_SPDY)) {
michael@0 434 LOG(("nsHttpConnection::SetupSSL Allow SPDY NPN selection"));
michael@0 435 for (uint32_t index = 0; index < SpdyInformation::kCount; ++index) {
michael@0 436 if (gHttpHandler->SpdyInfo()->ProtocolEnabled(index))
michael@0 437 protocolArray.AppendElement(
michael@0 438 gHttpHandler->SpdyInfo()->VersionString[index]);
michael@0 439 }
michael@0 440 }
michael@0 441
michael@0 442 if (NS_SUCCEEDED(ssl->SetNPNList(protocolArray))) {
michael@0 443 LOG(("nsHttpConnection::Init Setting up SPDY Negotiation OK"));
michael@0 444 mNPNComplete = false;
michael@0 445 }
michael@0 446 }
michael@0 447
michael@0 448 nsresult
michael@0 449 nsHttpConnection::AddTransaction(nsAHttpTransaction *httpTransaction,
michael@0 450 int32_t priority)
michael@0 451 {
michael@0 452 LOG(("nsHttpConnection::AddTransaction for SPDY"));
michael@0 453
michael@0 454 MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
michael@0 455 MOZ_ASSERT(mSpdySession && mUsingSpdyVersion,
michael@0 456 "AddTransaction to live http connection without spdy");
michael@0 457 MOZ_ASSERT(mTransaction,
michael@0 458 "AddTransaction to idle http connection");
michael@0 459
michael@0 460 if (!mSpdySession->AddStream(httpTransaction, priority)) {
michael@0 461 MOZ_ASSERT(false, "AddStream should never fail due to"
michael@0 462 "RoomForMore() admission check");
michael@0 463 return NS_ERROR_FAILURE;
michael@0 464 }
michael@0 465
michael@0 466 ResumeSend();
michael@0 467
michael@0 468 return NS_OK;
michael@0 469 }
michael@0 470
michael@0 471 void
michael@0 472 nsHttpConnection::Close(nsresult reason)
michael@0 473 {
michael@0 474 LOG(("nsHttpConnection::Close [this=%p reason=%x]\n", this, reason));
michael@0 475
michael@0 476 MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
michael@0 477
michael@0 478 // Ensure TCP keepalive timer is stopped.
michael@0 479 if (mTCPKeepaliveTransitionTimer) {
michael@0 480 mTCPKeepaliveTransitionTimer->Cancel();
michael@0 481 mTCPKeepaliveTransitionTimer = nullptr;
michael@0 482 }
michael@0 483
michael@0 484 if (NS_FAILED(reason)) {
michael@0 485 if (mIdleMonitoring)
michael@0 486 EndIdleMonitoring();
michael@0 487
michael@0 488 if (mSocketTransport) {
michael@0 489 mSocketTransport->SetEventSink(nullptr, nullptr);
michael@0 490
michael@0 491 // If there are bytes sitting in the input queue then read them
michael@0 492 // into a junk buffer to avoid generating a tcp rst by closing a
michael@0 493 // socket with data pending. TLS is a classic case of this where
michael@0 494 // a Alert record might be superfulous to a clean HTTP/SPDY shutdown.
michael@0 495 // Never block to do this and limit it to a small amount of data.
michael@0 496 if (mSocketIn) {
michael@0 497 char buffer[4000];
michael@0 498 uint32_t count, total = 0;
michael@0 499 nsresult rv;
michael@0 500 do {
michael@0 501 rv = mSocketIn->Read(buffer, 4000, &count);
michael@0 502 if (NS_SUCCEEDED(rv))
michael@0 503 total += count;
michael@0 504 }
michael@0 505 while (NS_SUCCEEDED(rv) && count > 0 && total < 64000);
michael@0 506 LOG(("nsHttpConnection::Close drained %d bytes\n", total));
michael@0 507 }
michael@0 508
michael@0 509 mSocketTransport->SetSecurityCallbacks(nullptr);
michael@0 510 mSocketTransport->Close(reason);
michael@0 511 if (mSocketOut)
michael@0 512 mSocketOut->AsyncWait(nullptr, 0, 0, nullptr);
michael@0 513 }
michael@0 514 mKeepAlive = false;
michael@0 515 }
michael@0 516 }
michael@0 517
michael@0 518 // called on the socket thread
michael@0 519 nsresult
michael@0 520 nsHttpConnection::ProxyStartSSL()
michael@0 521 {
michael@0 522 LOG(("nsHttpConnection::ProxyStartSSL [this=%p]\n", this));
michael@0 523 MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
michael@0 524
michael@0 525 nsCOMPtr<nsISupports> securityInfo;
michael@0 526 nsresult rv = mSocketTransport->GetSecurityInfo(getter_AddRefs(securityInfo));
michael@0 527 if (NS_FAILED(rv)) return rv;
michael@0 528
michael@0 529 nsCOMPtr<nsISSLSocketControl> ssl = do_QueryInterface(securityInfo, &rv);
michael@0 530 if (NS_FAILED(rv)) return rv;
michael@0 531
michael@0 532 return ssl->ProxyStartSSL();
michael@0 533 }
michael@0 534
michael@0 535 void
michael@0 536 nsHttpConnection::DontReuse()
michael@0 537 {
michael@0 538 mKeepAliveMask = false;
michael@0 539 mKeepAlive = false;
michael@0 540 mDontReuse = true;
michael@0 541 mIdleTimeout = 0;
michael@0 542 if (mSpdySession)
michael@0 543 mSpdySession->DontReuse();
michael@0 544 }
michael@0 545
michael@0 546 // Checked by the Connection Manager before scheduling a pipelined transaction
michael@0 547 bool
michael@0 548 nsHttpConnection::SupportsPipelining()
michael@0 549 {
michael@0 550 if (mTransaction &&
michael@0 551 mTransaction->PipelineDepth() >= mRemainingConnectionUses) {
michael@0 552 LOG(("nsHttpConnection::SupportsPipelining this=%p deny pipeline "
michael@0 553 "because current depth %d exceeds max remaining uses %d\n",
michael@0 554 this, mTransaction->PipelineDepth(), mRemainingConnectionUses));
michael@0 555 return false;
michael@0 556 }
michael@0 557 return mSupportsPipelining && IsKeepAlive() && !mDontReuse;
michael@0 558 }
michael@0 559
michael@0 560 bool
michael@0 561 nsHttpConnection::CanReuse()
michael@0 562 {
michael@0 563 if (mDontReuse)
michael@0 564 return false;
michael@0 565
michael@0 566 if ((mTransaction ? mTransaction->PipelineDepth() : 0) >=
michael@0 567 mRemainingConnectionUses) {
michael@0 568 return false;
michael@0 569 }
michael@0 570
michael@0 571 bool canReuse;
michael@0 572
michael@0 573 if (mSpdySession)
michael@0 574 canReuse = mSpdySession->CanReuse();
michael@0 575 else
michael@0 576 canReuse = IsKeepAlive();
michael@0 577
michael@0 578 canReuse = canReuse && (IdleTime() < mIdleTimeout) && IsAlive();
michael@0 579
michael@0 580 // An idle persistent connection should not have data waiting to be read
michael@0 581 // before a request is sent. Data here is likely a 408 timeout response
michael@0 582 // which we would deal with later on through the restart logic, but that
michael@0 583 // path is more expensive than just closing the socket now.
michael@0 584
michael@0 585 uint64_t dataSize;
michael@0 586 if (canReuse && mSocketIn && !mUsingSpdyVersion && mHttp1xTransactionCount &&
michael@0 587 NS_SUCCEEDED(mSocketIn->Available(&dataSize)) && dataSize) {
michael@0 588 LOG(("nsHttpConnection::CanReuse %p %s"
michael@0 589 "Socket not reusable because read data pending (%llu) on it.\n",
michael@0 590 this, mConnInfo->Host(), dataSize));
michael@0 591 canReuse = false;
michael@0 592 }
michael@0 593 return canReuse;
michael@0 594 }
michael@0 595
michael@0 596 bool
michael@0 597 nsHttpConnection::CanDirectlyActivate()
michael@0 598 {
michael@0 599 // return true if a new transaction can be addded to ths connection at any
michael@0 600 // time through Activate(). In practice this means this is a healthy SPDY
michael@0 601 // connection with room for more concurrent streams.
michael@0 602
michael@0 603 return UsingSpdy() && CanReuse() &&
michael@0 604 mSpdySession && mSpdySession->RoomForMoreStreams();
michael@0 605 }
michael@0 606
michael@0 607 PRIntervalTime
michael@0 608 nsHttpConnection::IdleTime()
michael@0 609 {
michael@0 610 return mSpdySession ?
michael@0 611 mSpdySession->IdleTime() : (PR_IntervalNow() - mLastReadTime);
michael@0 612 }
michael@0 613
michael@0 614 // returns the number of seconds left before the allowable idle period
michael@0 615 // expires, or 0 if the period has already expied.
michael@0 616 uint32_t
michael@0 617 nsHttpConnection::TimeToLive()
michael@0 618 {
michael@0 619 if (IdleTime() >= mIdleTimeout)
michael@0 620 return 0;
michael@0 621 uint32_t timeToLive = PR_IntervalToSeconds(mIdleTimeout - IdleTime());
michael@0 622
michael@0 623 // a positive amount of time can be rounded to 0. Because 0 is used
michael@0 624 // as the expiration signal, round all values from 0 to 1 up to 1.
michael@0 625 if (!timeToLive)
michael@0 626 timeToLive = 1;
michael@0 627 return timeToLive;
michael@0 628 }
michael@0 629
michael@0 630 bool
michael@0 631 nsHttpConnection::IsAlive()
michael@0 632 {
michael@0 633 if (!mSocketTransport)
michael@0 634 return false;
michael@0 635
michael@0 636 // SocketTransport::IsAlive can run the SSL state machine, so make sure
michael@0 637 // the NPN options are set before that happens.
michael@0 638 SetupSSL(mTransactionCaps);
michael@0 639
michael@0 640 bool alive;
michael@0 641 nsresult rv = mSocketTransport->IsAlive(&alive);
michael@0 642 if (NS_FAILED(rv))
michael@0 643 alive = false;
michael@0 644
michael@0 645 //#define TEST_RESTART_LOGIC
michael@0 646 #ifdef TEST_RESTART_LOGIC
michael@0 647 if (!alive) {
michael@0 648 LOG(("pretending socket is still alive to test restart logic\n"));
michael@0 649 alive = true;
michael@0 650 }
michael@0 651 #endif
michael@0 652
michael@0 653 return alive;
michael@0 654 }
michael@0 655
michael@0 656 bool
michael@0 657 nsHttpConnection::SupportsPipelining(nsHttpResponseHead *responseHead)
michael@0 658 {
michael@0 659 // SPDY supports infinite parallelism, so no need to pipeline.
michael@0 660 if (mUsingSpdyVersion)
michael@0 661 return false;
michael@0 662
michael@0 663 // assuming connection is HTTP/1.1 with keep-alive enabled
michael@0 664 if (mConnInfo->UsingHttpProxy() && !mConnInfo->UsingConnect()) {
michael@0 665 // XXX check for bad proxy servers...
michael@0 666 return true;
michael@0 667 }
michael@0 668
michael@0 669 // check for bad origin servers
michael@0 670 const char *val = responseHead->PeekHeader(nsHttp::Server);
michael@0 671
michael@0 672 // If there is no server header we will assume it should not be banned
michael@0 673 // as facebook and some other prominent sites do this
michael@0 674 if (!val)
michael@0 675 return true;
michael@0 676
michael@0 677 // The blacklist is indexed by the first character. All of these servers are
michael@0 678 // known to return their identifier as the first thing in the server string,
michael@0 679 // so we can do a leading match.
michael@0 680
michael@0 681 static const char *bad_servers[26][6] = {
michael@0 682 { nullptr }, { nullptr }, { nullptr }, { nullptr }, // a - d
michael@0 683 { "EFAServer/", nullptr }, // e
michael@0 684 { nullptr }, { nullptr }, { nullptr }, { nullptr }, // f - i
michael@0 685 { nullptr }, { nullptr }, { nullptr }, // j - l
michael@0 686 { "Microsoft-IIS/4.", "Microsoft-IIS/5.", nullptr }, // m
michael@0 687 { "Netscape-Enterprise/3.", "Netscape-Enterprise/4.",
michael@0 688 "Netscape-Enterprise/5.", "Netscape-Enterprise/6.", nullptr }, // n
michael@0 689 { nullptr }, { nullptr }, { nullptr }, { nullptr }, // o - r
michael@0 690 { nullptr }, { nullptr }, { nullptr }, { nullptr }, // s - v
michael@0 691 { "WebLogic 3.", "WebLogic 4.","WebLogic 5.", "WebLogic 6.",
michael@0 692 "Winstone Servlet Engine v0.", nullptr }, // w
michael@0 693 { nullptr }, { nullptr }, { nullptr } // x - z
michael@0 694 };
michael@0 695
michael@0 696 int index = val[0] - 'A'; // the whole table begins with capital letters
michael@0 697 if ((index >= 0) && (index <= 25))
michael@0 698 {
michael@0 699 for (int i = 0; bad_servers[index][i] != nullptr; i++) {
michael@0 700 if (!PL_strncmp (val, bad_servers[index][i], strlen (bad_servers[index][i]))) {
michael@0 701 LOG(("looks like this server does not support pipelining"));
michael@0 702 gHttpHandler->ConnMgr()->PipelineFeedbackInfo(
michael@0 703 mConnInfo, nsHttpConnectionMgr::RedBannedServer, this , 0);
michael@0 704 return false;
michael@0 705 }
michael@0 706 }
michael@0 707 }
michael@0 708
michael@0 709 // ok, let's allow pipelining to this server
michael@0 710 return true;
michael@0 711 }
michael@0 712
michael@0 713 //----------------------------------------------------------------------------
michael@0 714 // nsHttpConnection::nsAHttpConnection compatible methods
michael@0 715 //----------------------------------------------------------------------------
michael@0 716
michael@0 717 nsresult
michael@0 718 nsHttpConnection::OnHeadersAvailable(nsAHttpTransaction *trans,
michael@0 719 nsHttpRequestHead *requestHead,
michael@0 720 nsHttpResponseHead *responseHead,
michael@0 721 bool *reset)
michael@0 722 {
michael@0 723 LOG(("nsHttpConnection::OnHeadersAvailable [this=%p trans=%p response-head=%p]\n",
michael@0 724 this, trans, responseHead));
michael@0 725
michael@0 726 MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
michael@0 727 NS_ENSURE_ARG_POINTER(trans);
michael@0 728 MOZ_ASSERT(responseHead, "No response head?");
michael@0 729
michael@0 730 // we won't change our keep-alive policy unless the server has explicitly
michael@0 731 // told us to do so.
michael@0 732
michael@0 733 // inspect the connection headers for keep-alive info provided the
michael@0 734 // transaction completed successfully. In the case of a non-sensical close
michael@0 735 // and keep-alive favor the close out of conservatism.
michael@0 736
michael@0 737 bool explicitKeepAlive = false;
michael@0 738 bool explicitClose = responseHead->HasHeaderValue(nsHttp::Connection, "close") ||
michael@0 739 responseHead->HasHeaderValue(nsHttp::Proxy_Connection, "close");
michael@0 740 if (!explicitClose)
michael@0 741 explicitKeepAlive = responseHead->HasHeaderValue(nsHttp::Connection, "keep-alive") ||
michael@0 742 responseHead->HasHeaderValue(nsHttp::Proxy_Connection, "keep-alive");
michael@0 743
michael@0 744 // deal with 408 Server Timeouts
michael@0 745 uint16_t responseStatus = responseHead->Status();
michael@0 746 static const PRIntervalTime k1000ms = PR_MillisecondsToInterval(1000);
michael@0 747 if (responseStatus == 408) {
michael@0 748 // If this error could be due to a persistent connection reuse then
michael@0 749 // we pass an error code of NS_ERROR_NET_RESET to
michael@0 750 // trigger the transaction 'restart' mechanism. We tell it to reset its
michael@0 751 // response headers so that it will be ready to receive the new response.
michael@0 752 if (mIsReused && ((PR_IntervalNow() - mLastWriteTime) < k1000ms)) {
michael@0 753 Close(NS_ERROR_NET_RESET);
michael@0 754 *reset = true;
michael@0 755 return NS_OK;
michael@0 756 }
michael@0 757
michael@0 758 // timeouts that are not caused by persistent connection reuse should
michael@0 759 // not be retried for browser compatibility reasons. bug 907800. The
michael@0 760 // server driven close is implicit in the 408.
michael@0 761 explicitClose = true;
michael@0 762 explicitKeepAlive = false;
michael@0 763 }
michael@0 764
michael@0 765 // reset to default (the server may have changed since we last checked)
michael@0 766 mSupportsPipelining = false;
michael@0 767
michael@0 768 if ((responseHead->Version() < NS_HTTP_VERSION_1_1) ||
michael@0 769 (requestHead->Version() < NS_HTTP_VERSION_1_1)) {
michael@0 770 // HTTP/1.0 connections are by default NOT persistent
michael@0 771 if (explicitKeepAlive)
michael@0 772 mKeepAlive = true;
michael@0 773 else
michael@0 774 mKeepAlive = false;
michael@0 775
michael@0 776 // We need at least version 1.1 to use pipelines
michael@0 777 gHttpHandler->ConnMgr()->PipelineFeedbackInfo(
michael@0 778 mConnInfo, nsHttpConnectionMgr::RedVersionTooLow, this, 0);
michael@0 779 }
michael@0 780 else {
michael@0 781 // HTTP/1.1 connections are by default persistent
michael@0 782 if (explicitClose) {
michael@0 783 mKeepAlive = false;
michael@0 784
michael@0 785 // persistent connections are required for pipelining to work - if
michael@0 786 // this close was not pre-announced then generate the negative
michael@0 787 // BadExplicitClose feedback
michael@0 788 if (mRemainingConnectionUses > 1)
michael@0 789 gHttpHandler->ConnMgr()->PipelineFeedbackInfo(
michael@0 790 mConnInfo, nsHttpConnectionMgr::BadExplicitClose, this, 0);
michael@0 791 }
michael@0 792 else {
michael@0 793 mKeepAlive = true;
michael@0 794
michael@0 795 // Do not support pipelining when we are establishing
michael@0 796 // an SSL tunnel though an HTTP proxy. Pipelining support
michael@0 797 // determination must be based on comunication with the
michael@0 798 // target server in this case. See bug 422016 for futher
michael@0 799 // details.
michael@0 800 if (!mProxyConnectStream)
michael@0 801 mSupportsPipelining = SupportsPipelining(responseHead);
michael@0 802 }
michael@0 803 }
michael@0 804 mKeepAliveMask = mKeepAlive;
michael@0 805
michael@0 806 // Update the pipelining status in the connection info object
michael@0 807 // and also read it back. It is possible the ci status is
michael@0 808 // locked to false if pipelining has been banned on this ci due to
michael@0 809 // some kind of observed flaky behavior
michael@0 810 if (mSupportsPipelining) {
michael@0 811 // report the pipelining-compatible header to the connection manager
michael@0 812 // as positive feedback. This will undo 1 penalty point the host
michael@0 813 // may have accumulated in the past.
michael@0 814
michael@0 815 gHttpHandler->ConnMgr()->PipelineFeedbackInfo(
michael@0 816 mConnInfo, nsHttpConnectionMgr::NeutralExpectedOK, this, 0);
michael@0 817
michael@0 818 mSupportsPipelining =
michael@0 819 gHttpHandler->ConnMgr()->SupportsPipelining(mConnInfo);
michael@0 820 }
michael@0 821
michael@0 822 // If this connection is reserved for revalidations and we are
michael@0 823 // receiving a document that failed revalidation then switch the
michael@0 824 // classification to general to avoid pipelining more revalidations behind
michael@0 825 // it.
michael@0 826 if (mClassification == nsAHttpTransaction::CLASS_REVALIDATION &&
michael@0 827 responseStatus != 304) {
michael@0 828 mClassification = nsAHttpTransaction::CLASS_GENERAL;
michael@0 829 }
michael@0 830
michael@0 831 // if this connection is persistent, then the server may send a "Keep-Alive"
michael@0 832 // header specifying the maximum number of times the connection can be
michael@0 833 // reused as well as the maximum amount of time the connection can be idle
michael@0 834 // before the server will close it. we ignore the max reuse count, because
michael@0 835 // a "keep-alive" connection is by definition capable of being reused, and
michael@0 836 // we only care about being able to reuse it once. if a timeout is not
michael@0 837 // specified then we use our advertized timeout value.
michael@0 838 bool foundKeepAliveMax = false;
michael@0 839 if (mKeepAlive) {
michael@0 840 const char *val = responseHead->PeekHeader(nsHttp::Keep_Alive);
michael@0 841
michael@0 842 if (!mUsingSpdyVersion) {
michael@0 843 const char *cp = PL_strcasestr(val, "timeout=");
michael@0 844 if (cp)
michael@0 845 mIdleTimeout = PR_SecondsToInterval((uint32_t) atoi(cp + 8));
michael@0 846 else
michael@0 847 mIdleTimeout = gHttpHandler->IdleTimeout();
michael@0 848
michael@0 849 cp = PL_strcasestr(val, "max=");
michael@0 850 if (cp) {
michael@0 851 int val = atoi(cp + 4);
michael@0 852 if (val > 0) {
michael@0 853 foundKeepAliveMax = true;
michael@0 854 mRemainingConnectionUses = static_cast<uint32_t>(val);
michael@0 855 }
michael@0 856 }
michael@0 857 }
michael@0 858 else {
michael@0 859 mIdleTimeout = gHttpHandler->SpdyTimeout();
michael@0 860 }
michael@0 861
michael@0 862 LOG(("Connection can be reused [this=%p idle-timeout=%usec]\n",
michael@0 863 this, PR_IntervalToSeconds(mIdleTimeout)));
michael@0 864 }
michael@0 865
michael@0 866 if (!foundKeepAliveMax && mRemainingConnectionUses && !mUsingSpdyVersion)
michael@0 867 --mRemainingConnectionUses;
michael@0 868
michael@0 869 // If we're doing a proxy connect, we need to check whether or not
michael@0 870 // it was successful. If so, we have to reset the transaction and step-up
michael@0 871 // the socket connection if using SSL. Finally, we have to wake up the
michael@0 872 // socket write request.
michael@0 873 if (mProxyConnectStream) {
michael@0 874 MOZ_ASSERT(!mUsingSpdyVersion,
michael@0 875 "SPDY NPN Complete while using proxy connect stream");
michael@0 876 mProxyConnectStream = 0;
michael@0 877 if (responseStatus == 200) {
michael@0 878 LOG(("proxy CONNECT succeeded! ssl=%s\n",
michael@0 879 mConnInfo->UsingSSL() ? "true" :"false"));
michael@0 880 *reset = true;
michael@0 881 nsresult rv;
michael@0 882 if (mConnInfo->UsingSSL()) {
michael@0 883 rv = ProxyStartSSL();
michael@0 884 if (NS_FAILED(rv)) // XXX need to handle this for real
michael@0 885 LOG(("ProxyStartSSL failed [rv=%x]\n", rv));
michael@0 886 }
michael@0 887 mCompletedProxyConnect = true;
michael@0 888 mProxyConnectInProgress = false;
michael@0 889 rv = mSocketOut->AsyncWait(this, 0, 0, nullptr);
michael@0 890 // XXX what if this fails -- need to handle this error
michael@0 891 MOZ_ASSERT(NS_SUCCEEDED(rv), "mSocketOut->AsyncWait failed");
michael@0 892 }
michael@0 893 else {
michael@0 894 LOG(("proxy CONNECT failed! ssl=%s\n",
michael@0 895 mConnInfo->UsingSSL() ? "true" :"false"));
michael@0 896 mTransaction->SetProxyConnectFailed();
michael@0 897 }
michael@0 898 }
michael@0 899
michael@0 900 const char *upgradeReq = requestHead->PeekHeader(nsHttp::Upgrade);
michael@0 901 // Don't use persistent connection for Upgrade unless there's an auth failure:
michael@0 902 // some proxies expect to see auth response on persistent connection.
michael@0 903 if (upgradeReq && responseStatus != 401 && responseStatus != 407) {
michael@0 904 LOG(("HTTP Upgrade in play - disable keepalive\n"));
michael@0 905 DontReuse();
michael@0 906 }
michael@0 907
michael@0 908 if (responseStatus == 101) {
michael@0 909 const char *upgradeResp = responseHead->PeekHeader(nsHttp::Upgrade);
michael@0 910 if (!upgradeReq || !upgradeResp ||
michael@0 911 !nsHttp::FindToken(upgradeResp, upgradeReq,
michael@0 912 HTTP_HEADER_VALUE_SEPS)) {
michael@0 913 LOG(("HTTP 101 Upgrade header mismatch req = %s, resp = %s\n",
michael@0 914 upgradeReq, upgradeResp));
michael@0 915 Close(NS_ERROR_ABORT);
michael@0 916 }
michael@0 917 else {
michael@0 918 LOG(("HTTP Upgrade Response to %s\n", upgradeResp));
michael@0 919 }
michael@0 920 }
michael@0 921
michael@0 922 mLastHttpResponseVersion = responseHead->Version();
michael@0 923
michael@0 924 return NS_OK;
michael@0 925 }
michael@0 926
michael@0 927 bool
michael@0 928 nsHttpConnection::IsReused()
michael@0 929 {
michael@0 930 if (mIsReused)
michael@0 931 return true;
michael@0 932 if (!mConsiderReusedAfterInterval)
michael@0 933 return false;
michael@0 934
michael@0 935 // ReusedAfter allows a socket to be consider reused only after a certain
michael@0 936 // interval of time has passed
michael@0 937 return (PR_IntervalNow() - mConsiderReusedAfterEpoch) >=
michael@0 938 mConsiderReusedAfterInterval;
michael@0 939 }
michael@0 940
michael@0 941 void
michael@0 942 nsHttpConnection::SetIsReusedAfter(uint32_t afterMilliseconds)
michael@0 943 {
michael@0 944 mConsiderReusedAfterEpoch = PR_IntervalNow();
michael@0 945 mConsiderReusedAfterInterval = PR_MillisecondsToInterval(afterMilliseconds);
michael@0 946 }
michael@0 947
michael@0 948 nsresult
michael@0 949 nsHttpConnection::TakeTransport(nsISocketTransport **aTransport,
michael@0 950 nsIAsyncInputStream **aInputStream,
michael@0 951 nsIAsyncOutputStream **aOutputStream)
michael@0 952 {
michael@0 953 if (mUsingSpdyVersion)
michael@0 954 return NS_ERROR_FAILURE;
michael@0 955 if (mTransaction && !mTransaction->IsDone())
michael@0 956 return NS_ERROR_IN_PROGRESS;
michael@0 957 if (!(mSocketTransport && mSocketIn && mSocketOut))
michael@0 958 return NS_ERROR_NOT_INITIALIZED;
michael@0 959
michael@0 960 if (mInputOverflow)
michael@0 961 mSocketIn = mInputOverflow.forget();
michael@0 962
michael@0 963 // Change TCP Keepalive frequency to long-lived if currently short-lived.
michael@0 964 if (mTCPKeepaliveConfig == kTCPKeepaliveShortLivedConfig) {
michael@0 965 if (mTCPKeepaliveTransitionTimer) {
michael@0 966 mTCPKeepaliveTransitionTimer->Cancel();
michael@0 967 mTCPKeepaliveTransitionTimer = nullptr;
michael@0 968 }
michael@0 969 nsresult rv = StartLongLivedTCPKeepalives();
michael@0 970 LOG(("nsHttpConnection::TakeTransport [%p] calling "
michael@0 971 "StartLongLivedTCPKeepalives", this));
michael@0 972 if (NS_WARN_IF(NS_FAILED(rv))) {
michael@0 973 LOG(("nsHttpConnection::TakeTransport [%p] "
michael@0 974 "StartLongLivedTCPKeepalives failed rv[0x%x]", this, rv));
michael@0 975 }
michael@0 976 }
michael@0 977
michael@0 978 NS_IF_ADDREF(*aTransport = mSocketTransport);
michael@0 979 NS_IF_ADDREF(*aInputStream = mSocketIn);
michael@0 980 NS_IF_ADDREF(*aOutputStream = mSocketOut);
michael@0 981
michael@0 982 mSocketTransport->SetSecurityCallbacks(nullptr);
michael@0 983 mSocketTransport->SetEventSink(nullptr, nullptr);
michael@0 984 mSocketTransport = nullptr;
michael@0 985 mSocketIn = nullptr;
michael@0 986 mSocketOut = nullptr;
michael@0 987
michael@0 988 return NS_OK;
michael@0 989 }
michael@0 990
michael@0 991 uint32_t
michael@0 992 nsHttpConnection::ReadTimeoutTick(PRIntervalTime now)
michael@0 993 {
michael@0 994 MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
michael@0 995
michael@0 996 // make sure timer didn't tick before Activate()
michael@0 997 if (!mTransaction)
michael@0 998 return UINT32_MAX;
michael@0 999
michael@0 1000 // Spdy implements some timeout handling using the SPDY ping frame.
michael@0 1001 if (mSpdySession) {
michael@0 1002 return mSpdySession->ReadTimeoutTick(now);
michael@0 1003 }
michael@0 1004
michael@0 1005 uint32_t nextTickAfter = UINT32_MAX;
michael@0 1006 // Timeout if the response is taking too long to arrive.
michael@0 1007 if (mResponseTimeoutEnabled) {
michael@0 1008 NS_WARN_IF_FALSE(gHttpHandler->ResponseTimeoutEnabled(),
michael@0 1009 "Timing out a response, but response timeout is disabled!");
michael@0 1010
michael@0 1011 PRIntervalTime initialResponseDelta = now - mLastWriteTime;
michael@0 1012
michael@0 1013 if (initialResponseDelta > mTransaction->ResponseTimeout()) {
michael@0 1014 LOG(("canceling transaction: no response for %ums: timeout is %dms\n",
michael@0 1015 PR_IntervalToMilliseconds(initialResponseDelta),
michael@0 1016 PR_IntervalToMilliseconds(mTransaction->ResponseTimeout())));
michael@0 1017
michael@0 1018 mResponseTimeoutEnabled = false;
michael@0 1019
michael@0 1020 // This will also close the connection
michael@0 1021 CloseTransaction(mTransaction, NS_ERROR_NET_TIMEOUT);
michael@0 1022 return UINT32_MAX;
michael@0 1023 }
michael@0 1024 nextTickAfter = PR_IntervalToSeconds(mTransaction->ResponseTimeout()) -
michael@0 1025 PR_IntervalToSeconds(initialResponseDelta);
michael@0 1026 nextTickAfter = std::max(nextTickAfter, 1U);
michael@0 1027 }
michael@0 1028
michael@0 1029 if (!gHttpHandler->GetPipelineRescheduleOnTimeout())
michael@0 1030 return nextTickAfter;
michael@0 1031
michael@0 1032 PRIntervalTime delta = now - mLastReadTime;
michael@0 1033
michael@0 1034 // we replicate some of the checks both here and in OnSocketReadable() as
michael@0 1035 // they will be discovered under different conditions. The ones here
michael@0 1036 // will generally be discovered if we are totally hung and OSR does
michael@0 1037 // not get called at all, however OSR discovers them with lower latency
michael@0 1038 // if the issue is just very slow (but not stalled) reading.
michael@0 1039 //
michael@0 1040 // Right now we only take action if pipelining is involved, but this would
michael@0 1041 // be the place to add general read timeout handling if it is desired.
michael@0 1042
michael@0 1043 uint32_t pipelineDepth = mTransaction->PipelineDepth();
michael@0 1044 if (pipelineDepth > 1) {
michael@0 1045 // if we have pipelines outstanding (not just an idle connection)
michael@0 1046 // then get a fairly quick tick
michael@0 1047 nextTickAfter = 1;
michael@0 1048 }
michael@0 1049
michael@0 1050 if (delta >= gHttpHandler->GetPipelineRescheduleTimeout() &&
michael@0 1051 pipelineDepth > 1) {
michael@0 1052
michael@0 1053 // this just reschedules blocked transactions. no transaction
michael@0 1054 // is aborted completely.
michael@0 1055 LOG(("cancelling pipeline due to a %ums stall - depth %d\n",
michael@0 1056 PR_IntervalToMilliseconds(delta), pipelineDepth));
michael@0 1057
michael@0 1058 nsHttpPipeline *pipeline = mTransaction->QueryPipeline();
michael@0 1059 MOZ_ASSERT(pipeline, "pipelinedepth > 1 without pipeline");
michael@0 1060 // code this defensively for the moment and check for null in opt build
michael@0 1061 // This will reschedule blocked members of the pipeline, but the
michael@0 1062 // blocking transaction (i.e. response 0) will not be changed.
michael@0 1063 if (pipeline) {
michael@0 1064 pipeline->CancelPipeline(NS_ERROR_NET_TIMEOUT);
michael@0 1065 LOG(("Rescheduling the head of line blocked members of a pipeline "
michael@0 1066 "because reschedule-timeout idle interval exceeded"));
michael@0 1067 }
michael@0 1068 }
michael@0 1069
michael@0 1070 if (delta < gHttpHandler->GetPipelineTimeout())
michael@0 1071 return nextTickAfter;
michael@0 1072
michael@0 1073 if (pipelineDepth <= 1 && !mTransaction->PipelinePosition())
michael@0 1074 return nextTickAfter;
michael@0 1075
michael@0 1076 // nothing has transpired on this pipelined socket for many
michael@0 1077 // seconds. Call that a total stall and close the transaction.
michael@0 1078 // There is a chance the transaction will be restarted again
michael@0 1079 // depending on its state.. that will come back araound
michael@0 1080 // without pipelining on, so this won't loop.
michael@0 1081
michael@0 1082 LOG(("canceling transaction stalled for %ums on a pipeline "
michael@0 1083 "of depth %d and scheduled originally at pos %d\n",
michael@0 1084 PR_IntervalToMilliseconds(delta),
michael@0 1085 pipelineDepth, mTransaction->PipelinePosition()));
michael@0 1086
michael@0 1087 // This will also close the connection
michael@0 1088 CloseTransaction(mTransaction, NS_ERROR_NET_TIMEOUT);
michael@0 1089 return UINT32_MAX;
michael@0 1090 }
michael@0 1091
michael@0 1092 void
michael@0 1093 nsHttpConnection::UpdateTCPKeepalive(nsITimer *aTimer, void *aClosure)
michael@0 1094 {
michael@0 1095 MOZ_ASSERT(aTimer);
michael@0 1096 MOZ_ASSERT(aClosure);
michael@0 1097
michael@0 1098 nsHttpConnection *self = static_cast<nsHttpConnection*>(aClosure);
michael@0 1099
michael@0 1100 if (NS_WARN_IF(self->mUsingSpdyVersion)) {
michael@0 1101 return;
michael@0 1102 }
michael@0 1103
michael@0 1104 // Do not reduce keepalive probe frequency for idle connections.
michael@0 1105 if (self->mIdleMonitoring) {
michael@0 1106 return;
michael@0 1107 }
michael@0 1108
michael@0 1109 nsresult rv = self->StartLongLivedTCPKeepalives();
michael@0 1110 if (NS_WARN_IF(NS_FAILED(rv))) {
michael@0 1111 LOG(("nsHttpConnection::UpdateTCPKeepalive [%p] "
michael@0 1112 "StartLongLivedTCPKeepalives failed rv[0x%x]",
michael@0 1113 self, rv));
michael@0 1114 }
michael@0 1115 }
michael@0 1116
michael@0 1117 void
michael@0 1118 nsHttpConnection::GetSecurityInfo(nsISupports **secinfo)
michael@0 1119 {
michael@0 1120 MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
michael@0 1121
michael@0 1122 if (mSocketTransport) {
michael@0 1123 if (NS_FAILED(mSocketTransport->GetSecurityInfo(secinfo)))
michael@0 1124 *secinfo = nullptr;
michael@0 1125 }
michael@0 1126 }
michael@0 1127
michael@0 1128 void
michael@0 1129 nsHttpConnection::SetSecurityCallbacks(nsIInterfaceRequestor* aCallbacks)
michael@0 1130 {
michael@0 1131 MutexAutoLock lock(mCallbacksLock);
michael@0 1132 // This is called both on and off the main thread. For JS-implemented
michael@0 1133 // callbacks, we requires that the call happen on the main thread, but
michael@0 1134 // for C++-implemented callbacks we don't care. Use a pointer holder with
michael@0 1135 // strict checking disabled.
michael@0 1136 mCallbacks = new nsMainThreadPtrHolder<nsIInterfaceRequestor>(aCallbacks, false);
michael@0 1137 }
michael@0 1138
michael@0 1139 nsresult
michael@0 1140 nsHttpConnection::PushBack(const char *data, uint32_t length)
michael@0 1141 {
michael@0 1142 LOG(("nsHttpConnection::PushBack [this=%p, length=%d]\n", this, length));
michael@0 1143
michael@0 1144 if (mInputOverflow) {
michael@0 1145 NS_ERROR("nsHttpConnection::PushBack only one buffer supported");
michael@0 1146 return NS_ERROR_UNEXPECTED;
michael@0 1147 }
michael@0 1148
michael@0 1149 mInputOverflow = new nsPreloadedStream(mSocketIn, data, length);
michael@0 1150 return NS_OK;
michael@0 1151 }
michael@0 1152
michael@0 1153 nsresult
michael@0 1154 nsHttpConnection::ResumeSend()
michael@0 1155 {
michael@0 1156 LOG(("nsHttpConnection::ResumeSend [this=%p]\n", this));
michael@0 1157
michael@0 1158 MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
michael@0 1159
michael@0 1160 if (mSocketOut)
michael@0 1161 return mSocketOut->AsyncWait(this, 0, 0, nullptr);
michael@0 1162
michael@0 1163 NS_NOTREACHED("no socket output stream");
michael@0 1164 return NS_ERROR_UNEXPECTED;
michael@0 1165 }
michael@0 1166
michael@0 1167 nsresult
michael@0 1168 nsHttpConnection::ResumeRecv()
michael@0 1169 {
michael@0 1170 LOG(("nsHttpConnection::ResumeRecv [this=%p]\n", this));
michael@0 1171
michael@0 1172 MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
michael@0 1173
michael@0 1174 // the mLastReadTime timestamp is used for finding slowish readers
michael@0 1175 // and can be pretty sensitive. For that reason we actually reset it
michael@0 1176 // when we ask to read (resume recv()) so that when we get called back
michael@0 1177 // with actual read data in OnSocketReadable() we are only measuring
michael@0 1178 // the latency between those two acts and not all the processing that
michael@0 1179 // may get done before the ResumeRecv() call
michael@0 1180 mLastReadTime = PR_IntervalNow();
michael@0 1181
michael@0 1182 if (mSocketIn)
michael@0 1183 return mSocketIn->AsyncWait(this, 0, 0, nullptr);
michael@0 1184
michael@0 1185 NS_NOTREACHED("no socket input stream");
michael@0 1186 return NS_ERROR_UNEXPECTED;
michael@0 1187 }
michael@0 1188
michael@0 1189
michael@0 1190 class nsHttpConnectionForceRecv : public nsRunnable
michael@0 1191 {
michael@0 1192 public:
michael@0 1193 nsHttpConnectionForceRecv(nsHttpConnection *aConn)
michael@0 1194 : mConn(aConn) {}
michael@0 1195
michael@0 1196 NS_IMETHOD Run()
michael@0 1197 {
michael@0 1198 MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
michael@0 1199
michael@0 1200 if (!mConn->mSocketIn)
michael@0 1201 return NS_OK;
michael@0 1202 return mConn->OnInputStreamReady(mConn->mSocketIn);
michael@0 1203 }
michael@0 1204 private:
michael@0 1205 nsRefPtr<nsHttpConnection> mConn;
michael@0 1206 };
michael@0 1207
michael@0 1208 // trigger an asynchronous read
michael@0 1209 nsresult
michael@0 1210 nsHttpConnection::ForceRecv()
michael@0 1211 {
michael@0 1212 LOG(("nsHttpConnection::ForceRecv [this=%p]\n", this));
michael@0 1213 MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
michael@0 1214
michael@0 1215 return NS_DispatchToCurrentThread(new nsHttpConnectionForceRecv(this));
michael@0 1216 }
michael@0 1217
michael@0 1218 void
michael@0 1219 nsHttpConnection::BeginIdleMonitoring()
michael@0 1220 {
michael@0 1221 LOG(("nsHttpConnection::BeginIdleMonitoring [this=%p]\n", this));
michael@0 1222 MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
michael@0 1223 MOZ_ASSERT(!mTransaction, "BeginIdleMonitoring() while active");
michael@0 1224 MOZ_ASSERT(!mUsingSpdyVersion, "Idle monitoring of spdy not allowed");
michael@0 1225
michael@0 1226 LOG(("Entering Idle Monitoring Mode [this=%p]", this));
michael@0 1227 mIdleMonitoring = true;
michael@0 1228 if (mSocketIn)
michael@0 1229 mSocketIn->AsyncWait(this, 0, 0, nullptr);
michael@0 1230 }
michael@0 1231
michael@0 1232 void
michael@0 1233 nsHttpConnection::EndIdleMonitoring()
michael@0 1234 {
michael@0 1235 LOG(("nsHttpConnection::EndIdleMonitoring [this=%p]\n", this));
michael@0 1236 MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
michael@0 1237 MOZ_ASSERT(!mTransaction, "EndIdleMonitoring() while active");
michael@0 1238
michael@0 1239 if (mIdleMonitoring) {
michael@0 1240 LOG(("Leaving Idle Monitoring Mode [this=%p]", this));
michael@0 1241 mIdleMonitoring = false;
michael@0 1242 if (mSocketIn)
michael@0 1243 mSocketIn->AsyncWait(nullptr, 0, 0, nullptr);
michael@0 1244 }
michael@0 1245 }
michael@0 1246
michael@0 1247 //-----------------------------------------------------------------------------
michael@0 1248 // nsHttpConnection <private>
michael@0 1249 //-----------------------------------------------------------------------------
michael@0 1250
michael@0 1251 void
michael@0 1252 nsHttpConnection::CloseTransaction(nsAHttpTransaction *trans, nsresult reason)
michael@0 1253 {
michael@0 1254 LOG(("nsHttpConnection::CloseTransaction[this=%p trans=%x reason=%x]\n",
michael@0 1255 this, trans, reason));
michael@0 1256
michael@0 1257 MOZ_ASSERT(trans == mTransaction, "wrong transaction");
michael@0 1258 MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
michael@0 1259
michael@0 1260 if (mCurrentBytesRead > mMaxBytesRead)
michael@0 1261 mMaxBytesRead = mCurrentBytesRead;
michael@0 1262
michael@0 1263 // mask this error code because its not a real error.
michael@0 1264 if (reason == NS_BASE_STREAM_CLOSED)
michael@0 1265 reason = NS_OK;
michael@0 1266
michael@0 1267 if (mUsingSpdyVersion) {
michael@0 1268 DontReuse();
michael@0 1269 // if !mSpdySession then mUsingSpdyVersion must be false for canreuse()
michael@0 1270 mUsingSpdyVersion = 0;
michael@0 1271 mSpdySession = nullptr;
michael@0 1272 }
michael@0 1273
michael@0 1274 if (mTransaction) {
michael@0 1275 mHttp1xTransactionCount += mTransaction->Http1xTransactionCount();
michael@0 1276
michael@0 1277 mTransaction->Close(reason);
michael@0 1278 mTransaction = nullptr;
michael@0 1279 }
michael@0 1280
michael@0 1281 {
michael@0 1282 MutexAutoLock lock(mCallbacksLock);
michael@0 1283 mCallbacks = nullptr;
michael@0 1284 }
michael@0 1285
michael@0 1286 if (NS_FAILED(reason))
michael@0 1287 Close(reason);
michael@0 1288
michael@0 1289 // flag the connection as reused here for convenience sake. certainly
michael@0 1290 // it might be going away instead ;-)
michael@0 1291 mIsReused = true;
michael@0 1292 }
michael@0 1293
michael@0 1294 NS_METHOD
michael@0 1295 nsHttpConnection::ReadFromStream(nsIInputStream *input,
michael@0 1296 void *closure,
michael@0 1297 const char *buf,
michael@0 1298 uint32_t offset,
michael@0 1299 uint32_t count,
michael@0 1300 uint32_t *countRead)
michael@0 1301 {
michael@0 1302 // thunk for nsIInputStream instance
michael@0 1303 nsHttpConnection *conn = (nsHttpConnection *) closure;
michael@0 1304 return conn->OnReadSegment(buf, count, countRead);
michael@0 1305 }
michael@0 1306
michael@0 1307 nsresult
michael@0 1308 nsHttpConnection::OnReadSegment(const char *buf,
michael@0 1309 uint32_t count,
michael@0 1310 uint32_t *countRead)
michael@0 1311 {
michael@0 1312 if (count == 0) {
michael@0 1313 // some ReadSegments implementations will erroneously call the writer
michael@0 1314 // to consume 0 bytes worth of data. we must protect against this case
michael@0 1315 // or else we'd end up closing the socket prematurely.
michael@0 1316 NS_ERROR("bad ReadSegments implementation");
michael@0 1317 return NS_ERROR_FAILURE; // stop iterating
michael@0 1318 }
michael@0 1319
michael@0 1320 nsresult rv = mSocketOut->Write(buf, count, countRead);
michael@0 1321 if (NS_FAILED(rv))
michael@0 1322 mSocketOutCondition = rv;
michael@0 1323 else if (*countRead == 0)
michael@0 1324 mSocketOutCondition = NS_BASE_STREAM_CLOSED;
michael@0 1325 else {
michael@0 1326 mLastWriteTime = PR_IntervalNow();
michael@0 1327 mSocketOutCondition = NS_OK; // reset condition
michael@0 1328 if (!mProxyConnectInProgress)
michael@0 1329 mTotalBytesWritten += *countRead;
michael@0 1330 }
michael@0 1331
michael@0 1332 return mSocketOutCondition;
michael@0 1333 }
michael@0 1334
michael@0 1335 nsresult
michael@0 1336 nsHttpConnection::OnSocketWritable()
michael@0 1337 {
michael@0 1338 LOG(("nsHttpConnection::OnSocketWritable [this=%p] host=%s\n",
michael@0 1339 this, mConnInfo->Host()));
michael@0 1340
michael@0 1341 nsresult rv;
michael@0 1342 uint32_t n;
michael@0 1343 bool again = true;
michael@0 1344
michael@0 1345 do {
michael@0 1346 mSocketOutCondition = NS_OK;
michael@0 1347
michael@0 1348 // If we're doing a proxy connect, then we need to bypass calling into
michael@0 1349 // the transaction.
michael@0 1350 //
michael@0 1351 // NOTE: this code path can't be shared since the transaction doesn't
michael@0 1352 // implement nsIInputStream. doing so is not worth the added cost of
michael@0 1353 // extra indirections during normal reading.
michael@0 1354 //
michael@0 1355 if (mProxyConnectStream) {
michael@0 1356 LOG((" writing CONNECT request stream\n"));
michael@0 1357 rv = mProxyConnectStream->ReadSegments(ReadFromStream, this,
michael@0 1358 nsIOService::gDefaultSegmentSize,
michael@0 1359 &n);
michael@0 1360 }
michael@0 1361 else if (!EnsureNPNComplete()) {
michael@0 1362 // When SPDY is disabled this branch is not executed because Activate()
michael@0 1363 // sets mNPNComplete to true in that case.
michael@0 1364
michael@0 1365 // We are ready to proceed with SSL but the handshake is not done.
michael@0 1366 // When using NPN to negotiate between HTTPS and SPDY, we need to
michael@0 1367 // see the results of the handshake to know what bytes to send, so
michael@0 1368 // we cannot proceed with the request headers.
michael@0 1369
michael@0 1370 rv = NS_OK;
michael@0 1371 mSocketOutCondition = NS_BASE_STREAM_WOULD_BLOCK;
michael@0 1372 n = 0;
michael@0 1373 }
michael@0 1374 else {
michael@0 1375 if (!mReportedSpdy) {
michael@0 1376 mReportedSpdy = true;
michael@0 1377 gHttpHandler->ConnMgr()->ReportSpdyConnection(this, mEverUsedSpdy);
michael@0 1378 }
michael@0 1379
michael@0 1380 LOG((" writing transaction request stream\n"));
michael@0 1381 mProxyConnectInProgress = false;
michael@0 1382 rv = mTransaction->ReadSegments(this, nsIOService::gDefaultSegmentSize, &n);
michael@0 1383 }
michael@0 1384
michael@0 1385 LOG((" ReadSegments returned [rv=%x read=%u sock-cond=%x]\n",
michael@0 1386 rv, n, mSocketOutCondition));
michael@0 1387
michael@0 1388 // XXX some streams return NS_BASE_STREAM_CLOSED to indicate EOF.
michael@0 1389 if (rv == NS_BASE_STREAM_CLOSED && !mTransaction->IsDone()) {
michael@0 1390 rv = NS_OK;
michael@0 1391 n = 0;
michael@0 1392 }
michael@0 1393
michael@0 1394 if (NS_FAILED(rv)) {
michael@0 1395 // if the transaction didn't want to write any more data, then
michael@0 1396 // wait for the transaction to call ResumeSend.
michael@0 1397 if (rv == NS_BASE_STREAM_WOULD_BLOCK)
michael@0 1398 rv = NS_OK;
michael@0 1399 again = false;
michael@0 1400 }
michael@0 1401 else if (NS_FAILED(mSocketOutCondition)) {
michael@0 1402 if (mSocketOutCondition == NS_BASE_STREAM_WOULD_BLOCK)
michael@0 1403 rv = mSocketOut->AsyncWait(this, 0, 0, nullptr); // continue writing
michael@0 1404 else
michael@0 1405 rv = mSocketOutCondition;
michael@0 1406 again = false;
michael@0 1407 }
michael@0 1408 else if (n == 0) {
michael@0 1409 rv = NS_OK;
michael@0 1410
michael@0 1411 if (mTransaction) { // in case the ReadSegments stack called CloseTransaction()
michael@0 1412 //
michael@0 1413 // at this point we've written out the entire transaction, and now we
michael@0 1414 // must wait for the server's response. we manufacture a status message
michael@0 1415 // here to reflect the fact that we are waiting. this message will be
michael@0 1416 // trumped (overwritten) if the server responds quickly.
michael@0 1417 //
michael@0 1418 mTransaction->OnTransportStatus(mSocketTransport,
michael@0 1419 NS_NET_STATUS_WAITING_FOR,
michael@0 1420 0);
michael@0 1421
michael@0 1422 rv = ResumeRecv(); // start reading
michael@0 1423 }
michael@0 1424 again = false;
michael@0 1425 }
michael@0 1426 // write more to the socket until error or end-of-request...
michael@0 1427 } while (again);
michael@0 1428
michael@0 1429 return rv;
michael@0 1430 }
michael@0 1431
michael@0 1432 nsresult
michael@0 1433 nsHttpConnection::OnWriteSegment(char *buf,
michael@0 1434 uint32_t count,
michael@0 1435 uint32_t *countWritten)
michael@0 1436 {
michael@0 1437 if (count == 0) {
michael@0 1438 // some WriteSegments implementations will erroneously call the reader
michael@0 1439 // to provide 0 bytes worth of data. we must protect against this case
michael@0 1440 // or else we'd end up closing the socket prematurely.
michael@0 1441 NS_ERROR("bad WriteSegments implementation");
michael@0 1442 return NS_ERROR_FAILURE; // stop iterating
michael@0 1443 }
michael@0 1444
michael@0 1445 if (ChaosMode::isActive() && ChaosMode::randomUint32LessThan(2)) {
michael@0 1446 // read 1...count bytes
michael@0 1447 count = ChaosMode::randomUint32LessThan(count) + 1;
michael@0 1448 }
michael@0 1449
michael@0 1450 nsresult rv = mSocketIn->Read(buf, count, countWritten);
michael@0 1451 if (NS_FAILED(rv))
michael@0 1452 mSocketInCondition = rv;
michael@0 1453 else if (*countWritten == 0)
michael@0 1454 mSocketInCondition = NS_BASE_STREAM_CLOSED;
michael@0 1455 else
michael@0 1456 mSocketInCondition = NS_OK; // reset condition
michael@0 1457
michael@0 1458 return mSocketInCondition;
michael@0 1459 }
michael@0 1460
michael@0 1461 nsresult
michael@0 1462 nsHttpConnection::OnSocketReadable()
michael@0 1463 {
michael@0 1464 LOG(("nsHttpConnection::OnSocketReadable [this=%p]\n", this));
michael@0 1465
michael@0 1466 PRIntervalTime now = PR_IntervalNow();
michael@0 1467 PRIntervalTime delta = now - mLastReadTime;
michael@0 1468
michael@0 1469 // Reset mResponseTimeoutEnabled to stop response timeout checks.
michael@0 1470 mResponseTimeoutEnabled = false;
michael@0 1471
michael@0 1472 if (mKeepAliveMask && (delta >= mMaxHangTime)) {
michael@0 1473 LOG(("max hang time exceeded!\n"));
michael@0 1474 // give the handler a chance to create a new persistent connection to
michael@0 1475 // this host if we've been busy for too long.
michael@0 1476 mKeepAliveMask = false;
michael@0 1477 gHttpHandler->ProcessPendingQ(mConnInfo);
michael@0 1478 }
michael@0 1479
michael@0 1480 // Look for data being sent in bursts with large pauses. If the pauses
michael@0 1481 // are caused by server bottlenecks such as think-time, disk i/o, or
michael@0 1482 // cpu exhaustion (as opposed to network latency) then we generate negative
michael@0 1483 // pipelining feedback to prevent head of line problems
michael@0 1484
michael@0 1485 // Reduce the estimate of the time since last read by up to 1 RTT to
michael@0 1486 // accommodate exhausted sender TCP congestion windows or minor I/O delays.
michael@0 1487
michael@0 1488 if (delta > mRtt)
michael@0 1489 delta -= mRtt;
michael@0 1490 else
michael@0 1491 delta = 0;
michael@0 1492
michael@0 1493 static const PRIntervalTime k400ms = PR_MillisecondsToInterval(400);
michael@0 1494
michael@0 1495 if (delta >= (mRtt + gHttpHandler->GetPipelineRescheduleTimeout())) {
michael@0 1496 LOG(("Read delta ms of %u causing slow read major "
michael@0 1497 "event and pipeline cancellation",
michael@0 1498 PR_IntervalToMilliseconds(delta)));
michael@0 1499
michael@0 1500 gHttpHandler->ConnMgr()->PipelineFeedbackInfo(
michael@0 1501 mConnInfo, nsHttpConnectionMgr::BadSlowReadMajor, this, 0);
michael@0 1502
michael@0 1503 if (gHttpHandler->GetPipelineRescheduleOnTimeout() &&
michael@0 1504 mTransaction->PipelineDepth() > 1) {
michael@0 1505 nsHttpPipeline *pipeline = mTransaction->QueryPipeline();
michael@0 1506 MOZ_ASSERT(pipeline, "pipelinedepth > 1 without pipeline");
michael@0 1507 // code this defensively for the moment and check for null
michael@0 1508 // This will reschedule blocked members of the pipeline, but the
michael@0 1509 // blocking transaction (i.e. response 0) will not be changed.
michael@0 1510 if (pipeline) {
michael@0 1511 pipeline->CancelPipeline(NS_ERROR_NET_TIMEOUT);
michael@0 1512 LOG(("Rescheduling the head of line blocked members of a "
michael@0 1513 "pipeline because reschedule-timeout idle interval "
michael@0 1514 "exceeded"));
michael@0 1515 }
michael@0 1516 }
michael@0 1517 }
michael@0 1518 else if (delta > k400ms) {
michael@0 1519 gHttpHandler->ConnMgr()->PipelineFeedbackInfo(
michael@0 1520 mConnInfo, nsHttpConnectionMgr::BadSlowReadMinor, this, 0);
michael@0 1521 }
michael@0 1522
michael@0 1523 mLastReadTime = now;
michael@0 1524
michael@0 1525 nsresult rv;
michael@0 1526 uint32_t n;
michael@0 1527 bool again = true;
michael@0 1528
michael@0 1529 do {
michael@0 1530 if (!mProxyConnectInProgress && !mNPNComplete) {
michael@0 1531 // Unless we are setting up a tunnel via CONNECT, prevent reading
michael@0 1532 // from the socket until the results of NPN
michael@0 1533 // negotiation are known (which is determined from the write path).
michael@0 1534 // If the server speaks SPDY it is likely the readable data here is
michael@0 1535 // a spdy settings frame and without NPN it would be misinterpreted
michael@0 1536 // as HTTP/*
michael@0 1537
michael@0 1538 LOG(("nsHttpConnection::OnSocketReadable %p return due to inactive "
michael@0 1539 "tunnel setup but incomplete NPN state\n", this));
michael@0 1540 rv = NS_OK;
michael@0 1541 break;
michael@0 1542 }
michael@0 1543
michael@0 1544 rv = mTransaction->WriteSegments(this, nsIOService::gDefaultSegmentSize, &n);
michael@0 1545 if (NS_FAILED(rv)) {
michael@0 1546 // if the transaction didn't want to take any more data, then
michael@0 1547 // wait for the transaction to call ResumeRecv.
michael@0 1548 if (rv == NS_BASE_STREAM_WOULD_BLOCK)
michael@0 1549 rv = NS_OK;
michael@0 1550 again = false;
michael@0 1551 }
michael@0 1552 else {
michael@0 1553 mCurrentBytesRead += n;
michael@0 1554 mTotalBytesRead += n;
michael@0 1555 if (NS_FAILED(mSocketInCondition)) {
michael@0 1556 // continue waiting for the socket if necessary...
michael@0 1557 if (mSocketInCondition == NS_BASE_STREAM_WOULD_BLOCK)
michael@0 1558 rv = ResumeRecv();
michael@0 1559 else
michael@0 1560 rv = mSocketInCondition;
michael@0 1561 again = false;
michael@0 1562 }
michael@0 1563 }
michael@0 1564 // read more from the socket until error...
michael@0 1565 } while (again);
michael@0 1566
michael@0 1567 return rv;
michael@0 1568 }
michael@0 1569
michael@0 1570 nsresult
michael@0 1571 nsHttpConnection::SetupProxyConnect()
michael@0 1572 {
michael@0 1573 const char *val;
michael@0 1574
michael@0 1575 LOG(("nsHttpConnection::SetupProxyConnect [this=%p]\n", this));
michael@0 1576
michael@0 1577 NS_ENSURE_TRUE(!mProxyConnectStream, NS_ERROR_ALREADY_INITIALIZED);
michael@0 1578 MOZ_ASSERT(!mUsingSpdyVersion,
michael@0 1579 "SPDY NPN Complete while using proxy connect stream");
michael@0 1580
michael@0 1581 nsAutoCString buf;
michael@0 1582 nsresult rv = nsHttpHandler::GenerateHostPort(
michael@0 1583 nsDependentCString(mConnInfo->Host()), mConnInfo->Port(), buf);
michael@0 1584 if (NS_FAILED(rv))
michael@0 1585 return rv;
michael@0 1586
michael@0 1587 // CONNECT host:port HTTP/1.1
michael@0 1588 nsHttpRequestHead request;
michael@0 1589 request.SetMethod(NS_LITERAL_CSTRING("CONNECT"));
michael@0 1590 request.SetVersion(gHttpHandler->HttpVersion());
michael@0 1591 request.SetRequestURI(buf);
michael@0 1592 request.SetHeader(nsHttp::User_Agent, gHttpHandler->UserAgent());
michael@0 1593
michael@0 1594 // a CONNECT is always persistent
michael@0 1595 request.SetHeader(nsHttp::Proxy_Connection, NS_LITERAL_CSTRING("keep-alive"));
michael@0 1596 request.SetHeader(nsHttp::Connection, NS_LITERAL_CSTRING("keep-alive"));
michael@0 1597
michael@0 1598 // all HTTP/1.1 requests must include a Host header (even though it
michael@0 1599 // may seem redundant in this case; see bug 82388).
michael@0 1600 request.SetHeader(nsHttp::Host, buf);
michael@0 1601
michael@0 1602 val = mTransaction->RequestHead()->PeekHeader(nsHttp::Proxy_Authorization);
michael@0 1603 if (val) {
michael@0 1604 // we don't know for sure if this authorization is intended for the
michael@0 1605 // SSL proxy, so we add it just in case.
michael@0 1606 request.SetHeader(nsHttp::Proxy_Authorization, nsDependentCString(val));
michael@0 1607 }
michael@0 1608
michael@0 1609 buf.Truncate();
michael@0 1610 request.Flatten(buf, false);
michael@0 1611 buf.AppendLiteral("\r\n");
michael@0 1612
michael@0 1613 return NS_NewCStringInputStream(getter_AddRefs(mProxyConnectStream), buf);
michael@0 1614 }
michael@0 1615
michael@0 1616 nsresult
michael@0 1617 nsHttpConnection::StartShortLivedTCPKeepalives()
michael@0 1618 {
michael@0 1619 if (mUsingSpdyVersion) {
michael@0 1620 return NS_OK;
michael@0 1621 }
michael@0 1622 MOZ_ASSERT(mSocketTransport);
michael@0 1623 if (!mSocketTransport) {
michael@0 1624 return NS_ERROR_NOT_INITIALIZED;
michael@0 1625 }
michael@0 1626
michael@0 1627 nsresult rv = NS_OK;
michael@0 1628 int32_t idleTimeS = -1;
michael@0 1629 int32_t retryIntervalS = -1;
michael@0 1630 if (gHttpHandler->TCPKeepaliveEnabledForShortLivedConns()) {
michael@0 1631 // Set the idle time.
michael@0 1632 idleTimeS = gHttpHandler->GetTCPKeepaliveShortLivedIdleTime();
michael@0 1633 LOG(("nsHttpConnection::StartShortLivedTCPKeepalives[%p] "
michael@0 1634 "idle time[%ds].", this, idleTimeS));
michael@0 1635
michael@0 1636 retryIntervalS =
michael@0 1637 std::max<int32_t>((int32_t)PR_IntervalToSeconds(mRtt), 1);
michael@0 1638 rv = mSocketTransport->SetKeepaliveVals(idleTimeS, retryIntervalS);
michael@0 1639 if (NS_WARN_IF(NS_FAILED(rv))) {
michael@0 1640 return rv;
michael@0 1641 }
michael@0 1642 rv = mSocketTransport->SetKeepaliveEnabled(true);
michael@0 1643 mTCPKeepaliveConfig = kTCPKeepaliveShortLivedConfig;
michael@0 1644 } else {
michael@0 1645 rv = mSocketTransport->SetKeepaliveEnabled(false);
michael@0 1646 mTCPKeepaliveConfig = kTCPKeepaliveDisabled;
michael@0 1647 }
michael@0 1648 if (NS_WARN_IF(NS_FAILED(rv))) {
michael@0 1649 return rv;
michael@0 1650 }
michael@0 1651
michael@0 1652 // Start a timer to move to long-lived keepalive config.
michael@0 1653 if(!mTCPKeepaliveTransitionTimer) {
michael@0 1654 mTCPKeepaliveTransitionTimer =
michael@0 1655 do_CreateInstance("@mozilla.org/timer;1");
michael@0 1656 }
michael@0 1657
michael@0 1658 if (mTCPKeepaliveTransitionTimer) {
michael@0 1659 int32_t time = gHttpHandler->GetTCPKeepaliveShortLivedTime();
michael@0 1660
michael@0 1661 // Adjust |time| to ensure a full set of keepalive probes can be sent
michael@0 1662 // at the end of the short-lived phase.
michael@0 1663 if (gHttpHandler->TCPKeepaliveEnabledForShortLivedConns()) {
michael@0 1664 if (NS_WARN_IF(!gSocketTransportService)) {
michael@0 1665 return NS_ERROR_NOT_INITIALIZED;
michael@0 1666 }
michael@0 1667 int32_t probeCount = -1;
michael@0 1668 rv = gSocketTransportService->GetKeepaliveProbeCount(&probeCount);
michael@0 1669 if (NS_WARN_IF(NS_FAILED(rv))) {
michael@0 1670 return rv;
michael@0 1671 }
michael@0 1672 if (NS_WARN_IF(probeCount <= 0)) {
michael@0 1673 return NS_ERROR_UNEXPECTED;
michael@0 1674 }
michael@0 1675 // Add time for final keepalive probes, and 2 seconds for a buffer.
michael@0 1676 time += ((probeCount) * retryIntervalS) - (time % idleTimeS) + 2;
michael@0 1677 }
michael@0 1678 mTCPKeepaliveTransitionTimer->InitWithFuncCallback(
michael@0 1679 nsHttpConnection::UpdateTCPKeepalive,
michael@0 1680 this,
michael@0 1681 (uint32_t)time*1000,
michael@0 1682 nsITimer::TYPE_ONE_SHOT);
michael@0 1683 } else {
michael@0 1684 NS_WARNING("nsHttpConnection::StartShortLivedTCPKeepalives failed to "
michael@0 1685 "create timer.");
michael@0 1686 }
michael@0 1687
michael@0 1688 return NS_OK;
michael@0 1689 }
michael@0 1690
michael@0 1691 nsresult
michael@0 1692 nsHttpConnection::StartLongLivedTCPKeepalives()
michael@0 1693 {
michael@0 1694 MOZ_ASSERT(!mUsingSpdyVersion, "Don't use TCP Keepalive with SPDY!");
michael@0 1695 if (NS_WARN_IF(mUsingSpdyVersion)) {
michael@0 1696 return NS_OK;
michael@0 1697 }
michael@0 1698 MOZ_ASSERT(mSocketTransport);
michael@0 1699 if (!mSocketTransport) {
michael@0 1700 return NS_ERROR_NOT_INITIALIZED;
michael@0 1701 }
michael@0 1702
michael@0 1703 nsresult rv = NS_OK;
michael@0 1704 if (gHttpHandler->TCPKeepaliveEnabledForLongLivedConns()) {
michael@0 1705 // Increase the idle time.
michael@0 1706 int32_t idleTimeS = gHttpHandler->GetTCPKeepaliveLongLivedIdleTime();
michael@0 1707 LOG(("nsHttpConnection::StartLongLivedTCPKeepalives[%p] idle time[%ds]",
michael@0 1708 this, idleTimeS));
michael@0 1709
michael@0 1710 int32_t retryIntervalS =
michael@0 1711 std::max<int32_t>((int32_t)PR_IntervalToSeconds(mRtt), 1);
michael@0 1712 rv = mSocketTransport->SetKeepaliveVals(idleTimeS, retryIntervalS);
michael@0 1713 if (NS_WARN_IF(NS_FAILED(rv))) {
michael@0 1714 return rv;
michael@0 1715 }
michael@0 1716
michael@0 1717 // Ensure keepalive is enabled, if current status is disabled.
michael@0 1718 if (mTCPKeepaliveConfig == kTCPKeepaliveDisabled) {
michael@0 1719 rv = mSocketTransport->SetKeepaliveEnabled(true);
michael@0 1720 if (NS_WARN_IF(NS_FAILED(rv))) {
michael@0 1721 return rv;
michael@0 1722 }
michael@0 1723 }
michael@0 1724 mTCPKeepaliveConfig = kTCPKeepaliveLongLivedConfig;
michael@0 1725 } else {
michael@0 1726 rv = mSocketTransport->SetKeepaliveEnabled(false);
michael@0 1727 mTCPKeepaliveConfig = kTCPKeepaliveDisabled;
michael@0 1728 }
michael@0 1729
michael@0 1730 if (NS_WARN_IF(NS_FAILED(rv))) {
michael@0 1731 return rv;
michael@0 1732 }
michael@0 1733 return NS_OK;
michael@0 1734 }
michael@0 1735
michael@0 1736 nsresult
michael@0 1737 nsHttpConnection::DisableTCPKeepalives()
michael@0 1738 {
michael@0 1739 MOZ_ASSERT(mSocketTransport);
michael@0 1740 if (!mSocketTransport) {
michael@0 1741 return NS_ERROR_NOT_INITIALIZED;
michael@0 1742 }
michael@0 1743 LOG(("nsHttpConnection::DisableTCPKeepalives [%p]", this));
michael@0 1744 if (mTCPKeepaliveConfig != kTCPKeepaliveDisabled) {
michael@0 1745 nsresult rv = mSocketTransport->SetKeepaliveEnabled(false);
michael@0 1746 if (NS_WARN_IF(NS_FAILED(rv))) {
michael@0 1747 return rv;
michael@0 1748 }
michael@0 1749 mTCPKeepaliveConfig = kTCPKeepaliveDisabled;
michael@0 1750 }
michael@0 1751 if (mTCPKeepaliveTransitionTimer) {
michael@0 1752 mTCPKeepaliveTransitionTimer->Cancel();
michael@0 1753 mTCPKeepaliveTransitionTimer = nullptr;
michael@0 1754 }
michael@0 1755 return NS_OK;
michael@0 1756 }
michael@0 1757
michael@0 1758 //-----------------------------------------------------------------------------
michael@0 1759 // nsHttpConnection::nsISupports
michael@0 1760 //-----------------------------------------------------------------------------
michael@0 1761
michael@0 1762 NS_IMPL_ISUPPORTS(nsHttpConnection,
michael@0 1763 nsIInputStreamCallback,
michael@0 1764 nsIOutputStreamCallback,
michael@0 1765 nsITransportEventSink,
michael@0 1766 nsIInterfaceRequestor)
michael@0 1767
michael@0 1768 //-----------------------------------------------------------------------------
michael@0 1769 // nsHttpConnection::nsIInputStreamCallback
michael@0 1770 //-----------------------------------------------------------------------------
michael@0 1771
michael@0 1772 // called on the socket transport thread
michael@0 1773 NS_IMETHODIMP
michael@0 1774 nsHttpConnection::OnInputStreamReady(nsIAsyncInputStream *in)
michael@0 1775 {
michael@0 1776 MOZ_ASSERT(in == mSocketIn, "unexpected stream");
michael@0 1777 MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
michael@0 1778
michael@0 1779 if (mIdleMonitoring) {
michael@0 1780 MOZ_ASSERT(!mTransaction, "Idle Input Event While Active");
michael@0 1781
michael@0 1782 // The only read event that is protocol compliant for an idle connection
michael@0 1783 // is an EOF, which we check for with CanReuse(). If the data is
michael@0 1784 // something else then just ignore it and suspend checking for EOF -
michael@0 1785 // our normal timers or protocol stack are the place to deal with
michael@0 1786 // any exception logic.
michael@0 1787
michael@0 1788 if (!CanReuse()) {
michael@0 1789 LOG(("Server initiated close of idle conn %p\n", this));
michael@0 1790 gHttpHandler->ConnMgr()->CloseIdleConnection(this);
michael@0 1791 return NS_OK;
michael@0 1792 }
michael@0 1793
michael@0 1794 LOG(("Input data on idle conn %p, but not closing yet\n", this));
michael@0 1795 return NS_OK;
michael@0 1796 }
michael@0 1797
michael@0 1798 // if the transaction was dropped...
michael@0 1799 if (!mTransaction) {
michael@0 1800 LOG((" no transaction; ignoring event\n"));
michael@0 1801 return NS_OK;
michael@0 1802 }
michael@0 1803
michael@0 1804 nsresult rv = OnSocketReadable();
michael@0 1805 if (NS_FAILED(rv))
michael@0 1806 CloseTransaction(mTransaction, rv);
michael@0 1807
michael@0 1808 return NS_OK;
michael@0 1809 }
michael@0 1810
michael@0 1811 //-----------------------------------------------------------------------------
michael@0 1812 // nsHttpConnection::nsIOutputStreamCallback
michael@0 1813 //-----------------------------------------------------------------------------
michael@0 1814
michael@0 1815 NS_IMETHODIMP
michael@0 1816 nsHttpConnection::OnOutputStreamReady(nsIAsyncOutputStream *out)
michael@0 1817 {
michael@0 1818 MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
michael@0 1819 MOZ_ASSERT(out == mSocketOut, "unexpected socket");
michael@0 1820
michael@0 1821 // if the transaction was dropped...
michael@0 1822 if (!mTransaction) {
michael@0 1823 LOG((" no transaction; ignoring event\n"));
michael@0 1824 return NS_OK;
michael@0 1825 }
michael@0 1826
michael@0 1827 nsresult rv = OnSocketWritable();
michael@0 1828 if (NS_FAILED(rv))
michael@0 1829 CloseTransaction(mTransaction, rv);
michael@0 1830
michael@0 1831 return NS_OK;
michael@0 1832 }
michael@0 1833
michael@0 1834 //-----------------------------------------------------------------------------
michael@0 1835 // nsHttpConnection::nsITransportEventSink
michael@0 1836 //-----------------------------------------------------------------------------
michael@0 1837
michael@0 1838 NS_IMETHODIMP
michael@0 1839 nsHttpConnection::OnTransportStatus(nsITransport *trans,
michael@0 1840 nsresult status,
michael@0 1841 uint64_t progress,
michael@0 1842 uint64_t progressMax)
michael@0 1843 {
michael@0 1844 if (mTransaction)
michael@0 1845 mTransaction->OnTransportStatus(trans, status, progress);
michael@0 1846 return NS_OK;
michael@0 1847 }
michael@0 1848
michael@0 1849 //-----------------------------------------------------------------------------
michael@0 1850 // nsHttpConnection::nsIInterfaceRequestor
michael@0 1851 //-----------------------------------------------------------------------------
michael@0 1852
michael@0 1853 // not called on the socket transport thread
michael@0 1854 NS_IMETHODIMP
michael@0 1855 nsHttpConnection::GetInterface(const nsIID &iid, void **result)
michael@0 1856 {
michael@0 1857 // NOTE: This function is only called on the UI thread via sync proxy from
michael@0 1858 // the socket transport thread. If that weren't the case, then we'd
michael@0 1859 // have to worry about the possibility of mTransaction going away
michael@0 1860 // part-way through this function call. See CloseTransaction.
michael@0 1861
michael@0 1862 // NOTE - there is a bug here, the call to getinterface is proxied off the
michael@0 1863 // nss thread, not the ui thread as the above comment says. So there is
michael@0 1864 // indeed a chance of mTransaction going away. bug 615342
michael@0 1865
michael@0 1866 MOZ_ASSERT(PR_GetCurrentThread() != gSocketThread);
michael@0 1867
michael@0 1868 nsCOMPtr<nsIInterfaceRequestor> callbacks;
michael@0 1869 {
michael@0 1870 MutexAutoLock lock(mCallbacksLock);
michael@0 1871 callbacks = mCallbacks;
michael@0 1872 }
michael@0 1873 if (callbacks)
michael@0 1874 return callbacks->GetInterface(iid, result);
michael@0 1875 return NS_ERROR_NO_INTERFACE;
michael@0 1876 }
michael@0 1877
michael@0 1878 } // namespace mozilla::net
michael@0 1879 } // namespace mozilla

mercurial