bug 737470 patch 2 clone spdy/2 into spdy/3 r=honzab

--HG--
rename : netwerk/protocol/http/SpdySession2.cpp => netwerk/protocol/http/SpdySession3.cpp
rename : netwerk/protocol/http/SpdySession2.h => netwerk/protocol/http/SpdySession3.h
rename : netwerk/protocol/http/SpdyStream2.cpp => netwerk/protocol/http/SpdyStream3.cpp
rename : netwerk/protocol/http/SpdyStream2.h => netwerk/protocol/http/SpdyStream3.h
This commit is contained in:
Patrick McManus 2012-05-25 17:37:08 -04:00
parent 5ce388e01a
commit c02e9fa5c8
10 changed files with 3967 additions and 18 deletions

View File

@ -829,6 +829,8 @@ pref("network.http.fast-fallback-to-IPv4", true);
// Try and use SPDY when using SSL
pref("network.http.spdy.enabled", true);
pref("network.http.spdy.enabled.v2", true);
pref("network.http.spdy.enabled.v3", false);
pref("network.http.spdy.chunk-size", 4096);
pref("network.http.spdy.timeout", 180);
pref("network.http.spdy.coalesce-hostnames", true);

View File

@ -38,8 +38,11 @@
* ***** END LICENSE BLOCK ***** */
#include "nsHttp.h"
#include "nsHttpHandler.h"
#include "ASpdySession.h"
#include "SpdySession2.h"
#include "SpdySession3.h"
#include "mozilla/Telemetry.h"
@ -54,38 +57,44 @@ ASpdySession::NewSpdySession(PRUint32 version,
{
// This is a necko only interface, so we can enforce version
// requests as a precondition
NS_ABORT_IF_FALSE(version == SpdyInformation::SPDY_VERSION_2,
"Only version 2 implemented");
NS_ABORT_IF_FALSE(version == SpdyInformation::SPDY_VERSION_2 ||
version == SpdyInformation::SPDY_VERSION_3,
"Unsupported spdy version");
// Don't do a runtime check of IsSpdyV?Enabled() here because pref value
// may have changed since starting negotiation. The selected protocol comes
// from a list provided in the SERVER HELLO filtered by our acceptable
// versions, so there is no risk of the server ignoring our prefs.
Telemetry::Accumulate(Telemetry::SPDY_VERSION, version);
return new SpdySession2(aTransaction,
aTransport,
aPriority);
if (version == SpdyInformation::SPDY_VERSION_2)
return new SpdySession2(aTransaction, aTransport, aPriority);
return new SpdySession3(aTransaction, aTransport, aPriority);
}
SpdyInformation::SpdyInformation()
{
Version[0] = SPDY_VERSION_2;
VersionString[0] = NS_LITERAL_CSTRING("spdy/2");
AlternateProtocolString[0] = NS_LITERAL_CSTRING("443:npn-spdy/2");
// list the preferred version first
Version[0] = SPDY_VERSION_3;
VersionString[0] = NS_LITERAL_CSTRING("spdy/3");
AlternateProtocolString[0] = NS_LITERAL_CSTRING("443:npn-spdy/3");
Version[1] = 0;
VersionString[1] = EmptyCString();
AlternateProtocolString[1] = EmptyCString();
Version[1] = SPDY_VERSION_2;
VersionString[1] = NS_LITERAL_CSTRING("spdy/2");
AlternateProtocolString[1] = NS_LITERAL_CSTRING("443:npn-spdy/2");
}
bool
SpdyInformation::ProtocolEnabled(PRUint32 index)
{
// A future patch will make a spdy v2 specific pref
if (index == 0)
return true;
return gHttpHandler->IsSpdyV3Enabled();
// Right now there is no second protocol version
if (index == 1)
return false;
return gHttpHandler->IsSpdyV2Enabled();
NS_ABORT_IF_FALSE(false, "index out of range");
return false;
}

View File

@ -49,8 +49,12 @@ class nsISocketTransport;
namespace mozilla { namespace net {
// This is designed to handle 1 or 2 concrete protocol levels
// This is designed to handle up to 2 concrete protocol levels
// simultaneously
//
// Currently supported are v3 (preferred), and v2
// network.protocol.http.spdy.enabled.v2 (and v3) prefs can enable/disable
// them.
class ASpdySession : public nsAHttpTransaction
{
@ -93,7 +97,8 @@ public:
PRUint8 *result);
enum {
SPDY_VERSION_2 = 2
SPDY_VERSION_2 = 2,
SPDY_VERSION_3 = 3
};
PRUint8 Version[2];

View File

@ -81,6 +81,8 @@ CPPSRCS = \
ASpdySession.cpp \
SpdySession2.cpp \
SpdyStream2.cpp \
SpdySession3.cpp \
SpdyStream3.cpp \
$(NULL)
LOCAL_INCLUDES = \

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,373 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Mozilla.
*
* The Initial Developer of the Original Code is
* Mozilla Foundation.
* Portions created by the Initial Developer are Copyright (C) 2012
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Patrick McManus <mcmanus@ducksong.com>
*
* Alternatively, the contents of this file may be used under the terms of
* either of the GNU General Public License Version 2 or later (the "GPL"),
* or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
#ifndef mozilla_net_SpdySession3_h
#define mozilla_net_SpdySession3_h
// SPDY as defined by
// http://www.chromium.org/spdy/spdy-protocol/spdy-protocol-draft2
#include "ASpdySession.h"
#include "nsClassHashtable.h"
#include "nsDataHashtable.h"
#include "nsDeque.h"
#include "nsHashKeys.h"
#include "zlib.h"
class nsHttpConnection;
class nsISocketTransport;
namespace mozilla { namespace net {
class SpdyStream3;
class SpdySession3 : public ASpdySession
, public nsAHttpConnection
, public nsAHttpSegmentReader
, public nsAHttpSegmentWriter
{
public:
NS_DECL_ISUPPORTS
NS_DECL_NSAHTTPTRANSACTION
NS_DECL_NSAHTTPCONNECTION(mConnection)
NS_DECL_NSAHTTPSEGMENTREADER
NS_DECL_NSAHTTPSEGMENTWRITER
SpdySession3(nsAHttpTransaction *, nsISocketTransport *, PRInt32);
~SpdySession3();
bool AddStream(nsAHttpTransaction *, PRInt32);
bool CanReuse() { return !mShouldGoAway && !mClosed; }
bool RoomForMoreStreams();
// When the connection is active this is called every 1 second
void ReadTimeoutTick(PRIntervalTime now);
// Idle time represents time since "goodput".. e.g. a data or header frame
PRIntervalTime IdleTime();
PRUint32 RegisterStreamID(SpdyStream3 *);
const static PRUint8 kFlag_Control = 0x80;
const static PRUint8 kFlag_Data_FIN = 0x01;
const static PRUint8 kFlag_Data_UNI = 0x02;
const static PRUint8 kFlag_Data_ZLIB = 0x02;
// The protocol document for v2 specifies that the
// highest value (3) is the highest priority, but in
// reality 0 is the highest priority.
//
// Draft 3 notes here https://sites.google.com/a/chromium.org/dev/spdy/spdy-protocol/
// are the best guide to the mistake. Also see
// GetLowestPriority() and GetHighestPriority() in spdy_framer.h of
// chromium source.
const static PRUint8 kPri00 = 0 << 6; // highest
const static PRUint8 kPri01 = 1 << 6;
const static PRUint8 kPri02 = 2 << 6;
const static PRUint8 kPri03 = 3 << 6; // lowest
enum
{
CONTROL_TYPE_FIRST = 0,
CONTROL_TYPE_SYN_STREAM = 1,
CONTROL_TYPE_SYN_REPLY = 2,
CONTROL_TYPE_RST_STREAM = 3,
CONTROL_TYPE_SETTINGS = 4,
CONTROL_TYPE_NOOP = 5,
CONTROL_TYPE_PING = 6,
CONTROL_TYPE_GOAWAY = 7,
CONTROL_TYPE_HEADERS = 8,
CONTROL_TYPE_WINDOW_UPDATE = 9, /* no longer in v2 */
CONTROL_TYPE_LAST = 10
};
enum rstReason
{
RST_PROTOCOL_ERROR = 1,
RST_INVALID_STREAM = 2,
RST_REFUSED_STREAM = 3,
RST_UNSUPPORTED_VERSION = 4,
RST_CANCEL = 5,
RST_INTERNAL_ERROR = 6,
RST_FLOW_CONTROL_ERROR = 7,
RST_BAD_ASSOC_STREAM = 8
};
enum
{
SETTINGS_TYPE_UPLOAD_BW = 1, // kb/s
SETTINGS_TYPE_DOWNLOAD_BW = 2, // kb/s
SETTINGS_TYPE_RTT = 3, // ms
SETTINGS_TYPE_MAX_CONCURRENT = 4, // streams
SETTINGS_TYPE_CWND = 5, // packets
SETTINGS_TYPE_DOWNLOAD_RETRANS_RATE = 6, // percentage
SETTINGS_TYPE_INITIAL_WINDOW = 7 // bytes. Not used in v2.
};
// This should be big enough to hold all of your control packets,
// but if it needs to grow for huge headers it can do so dynamically.
// About 1% of requests to SPDY google services seem to be > 1000
// with all less than 2000.
const static PRUint32 kDefaultBufferSize = 2048;
// kDefaultQueueSize must be >= other queue size constants
const static PRUint32 kDefaultQueueSize = 16384;
const static PRUint32 kQueueMinimumCleanup = 8192;
const static PRUint32 kQueueTailRoom = 4096;
const static PRUint32 kQueueReserved = 1024;
const static PRUint32 kDefaultMaxConcurrent = 100;
const static PRUint32 kMaxStreamID = 0x7800000;
// This is a sentinel for a deleted stream. It is not a valid
// 31 bit stream ID.
const static PRUint32 kDeadStreamID = 0xffffdead;
static nsresult HandleSynStream(SpdySession3 *);
static nsresult HandleSynReply(SpdySession3 *);
static nsresult HandleRstStream(SpdySession3 *);
static nsresult HandleSettings(SpdySession3 *);
static nsresult HandleNoop(SpdySession3 *);
static nsresult HandlePing(SpdySession3 *);
static nsresult HandleGoAway(SpdySession3 *);
static nsresult HandleHeaders(SpdySession3 *);
static nsresult HandleWindowUpdate(SpdySession3 *);
static void EnsureBuffer(nsAutoArrayPtr<char> &,
PRUint32, PRUint32, PRUint32 &);
// For writing the SPDY data stream to LOG4
static void LogIO(SpdySession3 *, SpdyStream3 *, const char *,
const char *, PRUint32);
// an overload of nsAHttpConnection
void TransactionHasDataToWrite(nsAHttpTransaction *);
// a similar version for SpdyStream3
void TransactionHasDataToWrite(SpdyStream3 *);
// an overload of nsAHttpSegementReader
virtual nsresult CommitToSegmentSize(PRUint32 size);
private:
enum stateType {
BUFFERING_FRAME_HEADER,
BUFFERING_CONTROL_FRAME,
PROCESSING_DATA_FRAME,
DISCARDING_DATA_FRAME,
PROCESSING_CONTROL_SYN_REPLY,
PROCESSING_CONTROL_RST_STREAM
};
void DeterminePingThreshold();
nsresult HandleSynReplyForValidStream();
PRUint32 GetWriteQueueSize();
void ChangeDownstreamState(enum stateType);
void ResetDownstreamState();
nsresult DownstreamUncompress(char *, PRUint32);
void zlibInit();
nsresult FindHeader(nsCString, nsDependentCSubstring &);
nsresult ConvertHeaders(nsDependentCSubstring &,
nsDependentCSubstring &);
void GeneratePing(PRUint32);
void ClearPing(bool);
void GenerateRstStream(PRUint32, PRUint32);
void GenerateGoAway();
void CleanupStream(SpdyStream3 *, nsresult, rstReason);
void SetWriteCallbacks();
void FlushOutputQueue();
bool RoomForMoreConcurrent();
void ActivateStream(SpdyStream3 *);
void ProcessPending();
nsresult SetInputFrameDataStream(PRUint32);
bool VerifyStream(SpdyStream3 *, PRUint32);
void SetNeedsCleanup();
// a wrapper for all calls to the nshttpconnection level segment writer. Used
// to track network I/O for timeout purposes
nsresult NetworkRead(nsAHttpSegmentWriter *, char *, PRUint32, PRUint32 *);
static PLDHashOperator ShutdownEnumerator(nsAHttpTransaction *,
nsAutoPtr<SpdyStream3> &,
void *);
// This is intended to be nsHttpConnectionMgr:nsHttpConnectionHandle taken
// from the first transaction on this session. That object contains the
// pointer to the real network-level nsHttpConnection object.
nsRefPtr<nsAHttpConnection> mConnection;
// The underlying socket transport object is needed to propogate some events
nsISocketTransport *mSocketTransport;
// These are temporary state variables to hold the argument to
// Read/WriteSegments so it can be accessed by On(read/write)segment
// further up the stack.
nsAHttpSegmentReader *mSegmentReader;
nsAHttpSegmentWriter *mSegmentWriter;
PRUint32 mSendingChunkSize; /* the transmission chunk size */
PRUint32 mNextStreamID; /* 24 bits */
PRUint32 mConcurrentHighWater; /* max parallelism on session */
stateType mDownstreamState; /* in frame, between frames, etc.. */
// Maintain 5 indexes - one by stream ID, one by transaction ptr,
// one list of streams ready to write, one list of streams that are queued
// due to max parallelism settings, and one list of streams
// that must be given priority to write for window updates. The objects
// are not ref counted - they get destroyed
// by the nsClassHashtable implementation when they are removed from
// there.
nsDataHashtable<nsUint32HashKey, SpdyStream3 *> mStreamIDHash;
nsClassHashtable<nsPtrHashKey<nsAHttpTransaction>,
SpdyStream3> mStreamTransactionHash;
nsDeque mReadyForWrite;
nsDeque mQueuedStreams;
// UrgentForWrite is meant to carry window updates. They were defined in
// the v2 spec but apparently never implemented so are now scheduled to
// be removed. But they will be reintroduced for v3, so we will leave
// this queue in place to ease that transition.
nsDeque mUrgentForWrite;
// Compression contexts for header transport using deflate.
// SPDY compresses only HTTP headers and does not reset zlib in between
// frames.
z_stream mDownstreamZlib;
z_stream mUpstreamZlib;
// mInputFrameBuffer is used to store received control packets and the 8 bytes
// of header on data packets
PRUint32 mInputFrameBufferSize;
PRUint32 mInputFrameBufferUsed;
nsAutoArrayPtr<char> mInputFrameBuffer;
// mInputFrameDataSize/Read are used for tracking the amount of data consumed
// in a data frame. the data itself is not buffered in spdy
// The frame size is mInputFrameDataSize + the constant 8 byte header
PRUint32 mInputFrameDataSize;
PRUint32 mInputFrameDataRead;
bool mInputFrameDataLast; // This frame was marked FIN
// When a frame has been received that is addressed to a particular stream
// (e.g. a data frame after the stream-id has been decoded), this points
// to the stream.
SpdyStream3 *mInputFrameDataStream;
// mNeedsCleanup is a state variable to defer cleanup of a closed stream
// If needed, It is set in session::OnWriteSegments() and acted on and
// cleared when the stack returns to session::WriteSegments(). The stream
// cannot be destroyed directly out of OnWriteSegments because
// stream::writeSegments() is on the stack at that time.
SpdyStream3 *mNeedsCleanup;
// The CONTROL_TYPE value for a control frame
PRUint32 mFrameControlType;
// This reason code in the last processed RESET frame
PRUint32 mDownstreamRstReason;
// These are used for decompressing downstream spdy response headers
// This is done at the session level because sometimes the stream
// has already been canceled but the decompression still must happen
// to keep the zlib state correct for the next state of headers.
PRUint32 mDecompressBufferSize;
PRUint32 mDecompressBufferUsed;
nsAutoArrayPtr<char> mDecompressBuffer;
// for the conversion of downstream http headers into spdy formatted headers
nsCString mFlatHTTPResponseHeaders;
PRUint32 mFlatHTTPResponseHeadersOut;
// when set, the session will go away when it reaches 0 streams. This flag
// is set when: the stream IDs are running out (at either the client or the
// server), when DontReuse() is called, a RST that is not specific to a
// particular stream is received, a GOAWAY frame has been received from
// the server.
bool mShouldGoAway;
// the session has received a nsAHttpTransaction::Close() call
bool mClosed;
// the session received a GoAway frame with a valid GoAwayID
bool mCleanShutdown;
// If a GoAway message was received this is the ID of the last valid
// stream. 0 otherwise. (0 is never a valid stream id.)
PRUint32 mGoAwayID;
// The limit on number of concurrent streams for this session. Normally it
// is basically unlimited, but the SETTINGS control message from the
// server might bring it down.
PRUint32 mMaxConcurrent;
// The actual number of concurrent streams at this moment. Generally below
// mMaxConcurrent, but the max can be lowered in real time to a value
// below the current value
PRUint32 mConcurrent;
// The number of server initiated SYN-STREAMS, tracked for telemetry
PRUint32 mServerPushedResources;
// This is a output queue of bytes ready to be written to the SSL stream.
// When that streams returns WOULD_BLOCK on direct write the bytes get
// coalesced together here. This results in larger writes to the SSL layer.
// The buffer is not dynamically grown to accomodate stream writes, but
// does expand to accept infallible session wide frames like GoAway and RST.
PRUint32 mOutputQueueSize;
PRUint32 mOutputQueueUsed;
PRUint32 mOutputQueueSent;
nsAutoArrayPtr<char> mOutputQueueBuffer;
PRIntervalTime mPingThreshold;
PRIntervalTime mLastReadEpoch; // used for ping timeouts
PRIntervalTime mLastDataReadEpoch; // used for IdleTime()
PRIntervalTime mPingSentEpoch;
PRUint32 mNextPingID;
bool mPingThresholdExperiment;
};
}} // namespace mozilla::net
#endif // mozilla_net_SpdySession3_h

View File

@ -0,0 +1,926 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set sw=2 ts=8 et tw=80 : */
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Mozilla.
*
* The Initial Developer of the Original Code is
* Mozilla Foundation.
* Portions created by the Initial Developer are Copyright (C) 2012
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Patrick McManus <mcmanus@ducksong.com>
*
* Alternatively, the contents of this file may be used under the terms of
* either of the GNU General Public License Version 2 or later (the "GPL"),
* or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
#include "nsHttp.h"
#include "SpdySession3.h"
#include "SpdyStream3.h"
#include "nsAlgorithm.h"
#include "prnetdb.h"
#include "nsHttpRequestHead.h"
#include "mozilla/Telemetry.h"
#include "nsISocketTransport.h"
#include "nsISupportsPriority.h"
#ifdef DEBUG
// defined by the socket transport service while active
extern PRThread *gSocketThread;
#endif
namespace mozilla {
namespace net {
SpdyStream3::SpdyStream3(nsAHttpTransaction *httpTransaction,
SpdySession3 *spdySession,
nsISocketTransport *socketTransport,
PRUint32 chunkSize,
z_stream *compressionContext,
PRInt32 priority)
: mUpstreamState(GENERATING_SYN_STREAM),
mTransaction(httpTransaction),
mSession(spdySession),
mSocketTransport(socketTransport),
mSegmentReader(nsnull),
mSegmentWriter(nsnull),
mStreamID(0),
mChunkSize(chunkSize),
mSynFrameComplete(0),
mRequestBlockedOnRead(0),
mSentFinOnData(0),
mRecvdFin(0),
mFullyOpen(0),
mSentWaitingFor(0),
mTxInlineFrameSize(SpdySession3::kDefaultBufferSize),
mTxInlineFrameUsed(0),
mTxStreamFrameSize(0),
mZlib(compressionContext),
mRequestBodyLenRemaining(0),
mPriority(priority),
mTotalSent(0),
mTotalRead(0)
{
NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread");
LOG3(("SpdyStream3::SpdyStream3 %p", this));
mTxInlineFrame = new char[mTxInlineFrameSize];
}
SpdyStream3::~SpdyStream3()
{
mStreamID = SpdySession3::kDeadStreamID;
}
// ReadSegments() is used to write data down the socket. Generally, HTTP
// request data is pulled from the approriate transaction and
// converted to SPDY data. Sometimes control data like a window-update is
// generated instead.
nsresult
SpdyStream3::ReadSegments(nsAHttpSegmentReader *reader,
PRUint32 count,
PRUint32 *countRead)
{
LOG3(("SpdyStream3 %p ReadSegments reader=%p count=%d state=%x",
this, reader, count, mUpstreamState));
NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread");
nsresult rv = NS_ERROR_UNEXPECTED;
mRequestBlockedOnRead = 0;
switch (mUpstreamState) {
case GENERATING_SYN_STREAM:
case GENERATING_REQUEST_BODY:
case SENDING_REQUEST_BODY:
// Call into the HTTP Transaction to generate the HTTP request
// stream. That stream will show up in OnReadSegment().
mSegmentReader = reader;
rv = mTransaction->ReadSegments(this, count, countRead);
mSegmentReader = nsnull;
// Check to see if the transaction's request could be written out now.
// If not, mark the stream for callback when writing can proceed.
if (NS_SUCCEEDED(rv) &&
mUpstreamState == GENERATING_SYN_STREAM &&
!mSynFrameComplete)
mSession->TransactionHasDataToWrite(this);
// mTxinlineFrameUsed represents any queued un-sent frame. It might
// be 0 if there is no such frame, which is not a gurantee that we
// don't have more request body to send - just that any data that was
// sent comprised a complete SPDY frame. Likewise, a non 0 value is
// a queued, but complete, spdy frame length.
// Mark that we are blocked on read if the http transaction needs to
// provide more of the request message body and there is nothing queued
// for writing
if (rv == NS_BASE_STREAM_WOULD_BLOCK && !mTxInlineFrameUsed)
mRequestBlockedOnRead = 1;
if (!mTxInlineFrameUsed && NS_SUCCEEDED(rv) && (!*countRead)) {
LOG3(("ReadSegments %p: Sending request data complete, mUpstreamState=%x",
this, mUpstreamState));
if (mSentFinOnData) {
ChangeState(UPSTREAM_COMPLETE);
}
else {
GenerateDataFrameHeader(0, true);
ChangeState(SENDING_FIN_STREAM);
mSession->TransactionHasDataToWrite(this);
rv = NS_BASE_STREAM_WOULD_BLOCK;
}
}
break;
case SENDING_SYN_STREAM:
// We were trying to send the SYN-STREAM but were blocked from trying
// to transmit it the first time(s).
mSegmentReader = reader;
rv = TransmitFrame(nsnull, nsnull);
mSegmentReader = nsnull;
*countRead = 0;
if (NS_SUCCEEDED(rv)) {
NS_ABORT_IF_FALSE(!mTxInlineFrameUsed,
"Transmit Frame should be all or nothing");
if (mSentFinOnData) {
ChangeState(UPSTREAM_COMPLETE);
rv = NS_OK;
}
else {
rv = NS_BASE_STREAM_WOULD_BLOCK;
ChangeState(GENERATING_REQUEST_BODY);
mSession->TransactionHasDataToWrite(this);
}
}
break;
case SENDING_FIN_STREAM:
// We were trying to send the FIN-STREAM but were blocked from
// sending it out - try again.
if (!mSentFinOnData) {
mSegmentReader = reader;
rv = TransmitFrame(nsnull, nsnull);
mSegmentReader = nsnull;
NS_ABORT_IF_FALSE(NS_FAILED(rv) || !mTxInlineFrameUsed,
"Transmit Frame should be all or nothing");
if (NS_SUCCEEDED(rv))
ChangeState(UPSTREAM_COMPLETE);
}
else {
rv = NS_OK;
mTxInlineFrameUsed = 0; // cancel fin data packet
ChangeState(UPSTREAM_COMPLETE);
}
*countRead = 0;
// don't change OK to WOULD BLOCK. we are really done sending if OK
break;
case UPSTREAM_COMPLETE:
*countRead = 0;
rv = NS_OK;
break;
default:
NS_ABORT_IF_FALSE(false, "SpdyStream3::ReadSegments unknown state");
break;
}
return rv;
}
// WriteSegments() is used to read data off the socket. Generally this is
// just the SPDY frame header and from there the appropriate SPDYStream
// is identified from the Stream-ID. The http transaction associated with
// that read then pulls in the data directly.
nsresult
SpdyStream3::WriteSegments(nsAHttpSegmentWriter *writer,
PRUint32 count,
PRUint32 *countWritten)
{
LOG3(("SpdyStream3::WriteSegments %p count=%d state=%x",
this, count, mUpstreamState));
NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread");
NS_ABORT_IF_FALSE(!mSegmentWriter, "segment writer in progress");
mSegmentWriter = writer;
nsresult rv = mTransaction->WriteSegments(writer, count, countWritten);
mSegmentWriter = nsnull;
return rv;
}
PLDHashOperator
SpdyStream3::hdrHashEnumerate(const nsACString &key,
nsAutoPtr<nsCString> &value,
void *closure)
{
SpdyStream3 *self = static_cast<SpdyStream3 *>(closure);
self->CompressToFrame(key);
self->CompressToFrame(value.get());
return PL_DHASH_NEXT;
}
nsresult
SpdyStream3::ParseHttpRequestHeaders(const char *buf,
PRUint32 avail,
PRUint32 *countUsed)
{
// Returns NS_OK even if the headers are incomplete
// set mSynFrameComplete flag if they are complete
NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread");
NS_ABORT_IF_FALSE(mUpstreamState == GENERATING_SYN_STREAM, "wrong state");
LOG3(("SpdyStream3::ParseHttpRequestHeaders %p avail=%d state=%x",
this, avail, mUpstreamState));
mFlatHttpRequestHeaders.Append(buf, avail);
// We can use the simple double crlf because firefox is the
// only client we are parsing
PRInt32 endHeader = mFlatHttpRequestHeaders.Find("\r\n\r\n");
if (endHeader == kNotFound) {
// We don't have all the headers yet
LOG3(("SpdyStream3::ParseHttpRequestHeaders %p "
"Need more header bytes. Len = %d",
this, mFlatHttpRequestHeaders.Length()));
*countUsed = avail;
return NS_OK;
}
// We have recvd all the headers, trim the local
// buffer of the final empty line, and set countUsed to reflect
// the whole header has been consumed.
PRUint32 oldLen = mFlatHttpRequestHeaders.Length();
mFlatHttpRequestHeaders.SetLength(endHeader + 2);
*countUsed = avail - (oldLen - endHeader) + 4;
mSynFrameComplete = 1;
// It is now OK to assign a streamID that we are assured will
// be monotonically increasing amongst syn-streams on this
// session
mStreamID = mSession->RegisterStreamID(this);
NS_ABORT_IF_FALSE(mStreamID & 1,
"Spdy Stream Channel ID must be odd");
if (mStreamID >= 0x80000000) {
// streamID must fit in 31 bits. This is theoretically possible
// because stream ID assignment is asynchronous to stream creation
// because of the protocol requirement that the ID in syn-stream
// be monotonically increasing. In reality this is really not possible
// because new streams stop being added to a session with 0x10000000 / 2
// IDs still available and no race condition is going to bridge that gap,
// so we can be comfortable on just erroring out for correctness in that
// case.
LOG3(("Stream assigned out of range ID: 0x%X", mStreamID));
return NS_ERROR_UNEXPECTED;
}
// Now we need to convert the flat http headers into a set
// of SPDY headers.. writing to mTxInlineFrame{sz}
mTxInlineFrame[0] = SpdySession3::kFlag_Control;
mTxInlineFrame[1] = 2; /* version */
mTxInlineFrame[2] = 0;
mTxInlineFrame[3] = SpdySession3::CONTROL_TYPE_SYN_STREAM;
// 4 to 7 are length and flags, we'll fill that in later
PRUint32 networkOrderID = PR_htonl(mStreamID);
memcpy(mTxInlineFrame + 8, &networkOrderID, 4);
// this is the associated-to field, which is not used sending
// from the client in the http binding
memset (mTxInlineFrame + 12, 0, 4);
// Priority flags are the C0 mask of byte 16.
//
// The other 6 bits of 16 are unused. Spdy/3 will expand
// priority to 4 bits.
//
// When Spdy/3 implements WINDOW_UPDATE the lowest priority
// streams over a threshold (32?) should be given tiny
// receive windows, separate from their spdy priority
//
if (mPriority >= nsISupportsPriority::PRIORITY_LOW)
mTxInlineFrame[16] = SpdySession3::kPri03;
else if (mPriority >= nsISupportsPriority::PRIORITY_NORMAL)
mTxInlineFrame[16] = SpdySession3::kPri02;
else if (mPriority >= nsISupportsPriority::PRIORITY_HIGH)
mTxInlineFrame[16] = SpdySession3::kPri01;
else
mTxInlineFrame[16] = SpdySession3::kPri00;
mTxInlineFrame[17] = 0; /* unused */
const char *methodHeader = mTransaction->RequestHead()->Method().get();
nsCString hostHeader;
mTransaction->RequestHead()->GetHeader(nsHttp::Host, hostHeader);
nsCString versionHeader;
if (mTransaction->RequestHead()->Version() == NS_HTTP_VERSION_1_1)
versionHeader = NS_LITERAL_CSTRING("HTTP/1.1");
else
versionHeader = NS_LITERAL_CSTRING("HTTP/1.0");
nsClassHashtable<nsCStringHashKey, nsCString> hdrHash;
// use mRequestHead() to get a sense of how big to make the hash,
// even though we are parsing the actual text stream because
// it is legit to append headers.
hdrHash.Init(1 + (mTransaction->RequestHead()->Headers().Count() * 2));
const char *beginBuffer = mFlatHttpRequestHeaders.BeginReading();
// need to hash all the headers together to remove duplicates, special
// headers, etc..
PRInt32 crlfIndex = mFlatHttpRequestHeaders.Find("\r\n");
while (true) {
PRInt32 startIndex = crlfIndex + 2;
crlfIndex = mFlatHttpRequestHeaders.Find("\r\n", false, startIndex);
if (crlfIndex == -1)
break;
PRInt32 colonIndex = mFlatHttpRequestHeaders.Find(":", false, startIndex,
crlfIndex - startIndex);
if (colonIndex == -1)
break;
nsDependentCSubstring name = Substring(beginBuffer + startIndex,
beginBuffer + colonIndex);
// all header names are lower case in spdy
ToLowerCase(name);
if (name.Equals("method") ||
name.Equals("version") ||
name.Equals("scheme") ||
name.Equals("keep-alive") ||
name.Equals("accept-encoding") ||
name.Equals("te") ||
name.Equals("connection") ||
name.Equals("proxy-connection") ||
name.Equals("url"))
continue;
nsCString *val = hdrHash.Get(name);
if (!val) {
val = new nsCString();
hdrHash.Put(name, val);
}
PRInt32 valueIndex = colonIndex + 1;
while (valueIndex < crlfIndex && beginBuffer[valueIndex] == ' ')
++valueIndex;
nsDependentCSubstring v = Substring(beginBuffer + valueIndex,
beginBuffer + crlfIndex);
if (!val->IsEmpty())
val->Append(static_cast<char>(0));
val->Append(v);
if (name.Equals("content-length")) {
PRInt64 len;
if (nsHttp::ParseInt64(val->get(), nsnull, &len))
mRequestBodyLenRemaining = len;
}
}
mTxInlineFrameUsed = 18;
// Do not naively log the request headers here beacuse they might
// contain auth. The http transaction already logs the sanitized request
// headers at this same level so it is not necessary to do so here.
// The header block length
PRUint16 count = hdrHash.Count() + 4; /* method, scheme, url, version */
CompressToFrame(count);
// method, scheme, url, and version headers for request line
CompressToFrame(NS_LITERAL_CSTRING("method"));
CompressToFrame(methodHeader, strlen(methodHeader));
CompressToFrame(NS_LITERAL_CSTRING("scheme"));
CompressToFrame(NS_LITERAL_CSTRING("https"));
CompressToFrame(NS_LITERAL_CSTRING("url"));
CompressToFrame(mTransaction->RequestHead()->RequestURI());
CompressToFrame(NS_LITERAL_CSTRING("version"));
CompressToFrame(versionHeader);
hdrHash.Enumerate(hdrHashEnumerate, this);
CompressFlushFrame();
// 4 to 7 are length and flags, which we can now fill in
(reinterpret_cast<PRUint32 *>(mTxInlineFrame.get()))[1] =
PR_htonl(mTxInlineFrameUsed - 8);
NS_ABORT_IF_FALSE(!mTxInlineFrame[4],
"Size greater than 24 bits");
// Determine whether to put the fin bit on the syn stream frame or whether
// to wait for a data packet to put it on.
if (mTransaction->RequestHead()->Method() == nsHttp::Get ||
mTransaction->RequestHead()->Method() == nsHttp::Connect ||
mTransaction->RequestHead()->Method() == nsHttp::Head) {
// for GET, CONNECT, and HEAD place the fin bit right on the
// syn stream packet
mSentFinOnData = 1;
mTxInlineFrame[4] = SpdySession3::kFlag_Data_FIN;
}
else if (mTransaction->RequestHead()->Method() == nsHttp::Post ||
mTransaction->RequestHead()->Method() == nsHttp::Put ||
mTransaction->RequestHead()->Method() == nsHttp::Options) {
// place fin in a data frame even for 0 length messages, I've seen
// the google gateway be unhappy with fin-on-syn for 0 length POST
}
else if (!mRequestBodyLenRemaining) {
// for other HTTP extension methods, rely on the content-length
// to determine whether or not to put fin on syn
mSentFinOnData = 1;
mTxInlineFrame[4] = SpdySession3::kFlag_Data_FIN;
}
Telemetry::Accumulate(Telemetry::SPDY_SYN_SIZE, mTxInlineFrameUsed - 18);
// The size of the input headers is approximate
PRUint32 ratio =
(mTxInlineFrameUsed - 18) * 100 /
(11 + mTransaction->RequestHead()->RequestURI().Length() +
mFlatHttpRequestHeaders.Length());
Telemetry::Accumulate(Telemetry::SPDY_SYN_RATIO, ratio);
return NS_OK;
}
void
SpdyStream3::UpdateTransportReadEvents(PRUint32 count)
{
mTotalRead += count;
mTransaction->OnTransportStatus(mSocketTransport,
NS_NET_STATUS_RECEIVING_FROM,
mTotalRead);
}
void
SpdyStream3::UpdateTransportSendEvents(PRUint32 count)
{
mTotalSent += count;
if (mUpstreamState != SENDING_FIN_STREAM)
mTransaction->OnTransportStatus(mSocketTransport,
NS_NET_STATUS_SENDING_TO,
mTotalSent);
if (!mSentWaitingFor && !mRequestBodyLenRemaining) {
mSentWaitingFor = 1;
mTransaction->OnTransportStatus(mSocketTransport,
NS_NET_STATUS_WAITING_FOR,
LL_ZERO);
}
}
nsresult
SpdyStream3::TransmitFrame(const char *buf,
PRUint32 *countUsed)
{
// If TransmitFrame returns SUCCESS than all the data is sent (or at least
// buffered at the session level), if it returns WOULD_BLOCK then none of
// the data is sent.
// You can call this function with no data and no out parameter in order to
// flush internal buffers that were previously blocked on writing. You can
// of course feed new data to it as well.
NS_ABORT_IF_FALSE(mTxInlineFrameUsed, "empty stream frame in transmit");
NS_ABORT_IF_FALSE(mSegmentReader, "TransmitFrame with null mSegmentReader");
NS_ABORT_IF_FALSE((buf && countUsed) || (!buf && !countUsed),
"TransmitFrame arguments inconsistent");
PRUint32 transmittedCount;
nsresult rv;
LOG3(("SpdyStream3::TransmitFrame %p inline=%d stream=%d",
this, mTxInlineFrameUsed, mTxStreamFrameSize));
if (countUsed)
*countUsed = 0;
// In the (relatively common) event that we have a small amount of data
// split between the inlineframe and the streamframe, then move the stream
// data into the inlineframe via copy in order to coalesce into one write.
// Given the interaction with ssl this is worth the small copy cost.
if (mTxStreamFrameSize && mTxInlineFrameUsed &&
mTxStreamFrameSize < SpdySession3::kDefaultBufferSize &&
mTxInlineFrameUsed + mTxStreamFrameSize < mTxInlineFrameSize) {
LOG3(("Coalesce Transmit"));
memcpy (mTxInlineFrame + mTxInlineFrameUsed,
buf, mTxStreamFrameSize);
if (countUsed)
*countUsed += mTxStreamFrameSize;
mTxInlineFrameUsed += mTxStreamFrameSize;
mTxStreamFrameSize = 0;
}
rv = mSegmentReader->CommitToSegmentSize(mTxStreamFrameSize +
mTxInlineFrameUsed);
if (rv == NS_BASE_STREAM_WOULD_BLOCK)
mSession->TransactionHasDataToWrite(this);
if (NS_FAILED(rv)) // this will include WOULD_BLOCK
return rv;
// This function calls mSegmentReader->OnReadSegment to report the actual SPDY
// bytes through to the SpdySession3 and then the HttpConnection which calls
// the socket write function. It will accept all of the inline and stream
// data because of the above 'commitment' even if it has to buffer
rv = mSegmentReader->OnReadSegment(mTxInlineFrame, mTxInlineFrameUsed,
&transmittedCount);
LOG3(("SpdyStream3::TransmitFrame for inline session=%p "
"stream=%p result %x len=%d",
mSession, this, rv, transmittedCount));
NS_ABORT_IF_FALSE(rv != NS_BASE_STREAM_WOULD_BLOCK,
"inconsistent inline commitment result");
if (NS_FAILED(rv))
return rv;
NS_ABORT_IF_FALSE(transmittedCount == mTxInlineFrameUsed,
"inconsistent inline commitment count");
SpdySession3::LogIO(mSession, this, "Writing from Inline Buffer",
mTxInlineFrame, transmittedCount);
if (mTxStreamFrameSize) {
if (!buf) {
// this cannot happen
NS_ABORT_IF_FALSE(false, "Stream transmit with null buf argument to "
"TransmitFrame()");
LOG(("Stream transmit with null buf argument to TransmitFrame()\n"));
return NS_ERROR_UNEXPECTED;
}
rv = mSegmentReader->OnReadSegment(buf, mTxStreamFrameSize,
&transmittedCount);
LOG3(("SpdyStream3::TransmitFrame for regular session=%p "
"stream=%p result %x len=%d",
mSession, this, rv, transmittedCount));
NS_ABORT_IF_FALSE(rv != NS_BASE_STREAM_WOULD_BLOCK,
"inconsistent stream commitment result");
if (NS_FAILED(rv))
return rv;
NS_ABORT_IF_FALSE(transmittedCount == mTxStreamFrameSize,
"inconsistent stream commitment count");
SpdySession3::LogIO(mSession, this, "Writing from Transaction Buffer",
buf, transmittedCount);
*countUsed += mTxStreamFrameSize;
}
// calling this will trigger waiting_for if mRequestBodyLenRemaining is 0
UpdateTransportSendEvents(mTxInlineFrameUsed + mTxStreamFrameSize);
mTxInlineFrameUsed = 0;
mTxStreamFrameSize = 0;
return NS_OK;
}
void
SpdyStream3::ChangeState(enum stateType newState)
{
LOG3(("SpdyStream3::ChangeState() %p from %X to %X",
this, mUpstreamState, newState));
mUpstreamState = newState;
return;
}
void
SpdyStream3::GenerateDataFrameHeader(PRUint32 dataLength, bool lastFrame)
{
LOG3(("SpdyStream3::GenerateDataFrameHeader %p len=%d last=%d",
this, dataLength, lastFrame));
NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread");
NS_ABORT_IF_FALSE(!mTxInlineFrameUsed, "inline frame not empty");
NS_ABORT_IF_FALSE(!mTxStreamFrameSize, "stream frame not empty");
NS_ABORT_IF_FALSE(!(dataLength & 0xff000000), "datalength > 24 bits");
(reinterpret_cast<PRUint32 *>(mTxInlineFrame.get()))[0] = PR_htonl(mStreamID);
(reinterpret_cast<PRUint32 *>(mTxInlineFrame.get()))[1] =
PR_htonl(dataLength);
NS_ABORT_IF_FALSE(!(mTxInlineFrame[0] & 0x80),
"control bit set unexpectedly");
NS_ABORT_IF_FALSE(!mTxInlineFrame[4], "flag bits set unexpectedly");
mTxInlineFrameUsed = 8;
mTxStreamFrameSize = dataLength;
if (lastFrame) {
mTxInlineFrame[4] |= SpdySession3::kFlag_Data_FIN;
if (dataLength)
mSentFinOnData = 1;
}
}
void
SpdyStream3::CompressToFrame(const nsACString &str)
{
CompressToFrame(str.BeginReading(), str.Length());
}
void
SpdyStream3::CompressToFrame(const nsACString *str)
{
CompressToFrame(str->BeginReading(), str->Length());
}
// Dictionary taken from
// http://dev.chromium.org/spdy/spdy-protocol/spdy-protocol-draft2
// Name/Value Header Block Format
// spec indicates that the compression dictionary is not null terminated
// but in reality it is. see:
// https://groups.google.com/forum/#!topic/spdy-dev/2pWxxOZEIcs
const char *SpdyStream3::kDictionary =
"optionsgetheadpostputdeletetraceacceptaccept-charsetaccept-encodingaccept-"
"languageauthorizationexpectfromhostif-modified-sinceif-matchif-none-matchi"
"f-rangeif-unmodifiedsincemax-forwardsproxy-authorizationrangerefererteuser"
"-agent10010120020120220320420520630030130230330430530630740040140240340440"
"5406407408409410411412413414415416417500501502503504505accept-rangesageeta"
"glocationproxy-authenticatepublicretry-afterservervarywarningwww-authentic"
"ateallowcontent-basecontent-encodingcache-controlconnectiondatetrailertran"
"sfer-encodingupgradeviawarningcontent-languagecontent-lengthcontent-locati"
"oncontent-md5content-rangecontent-typeetagexpireslast-modifiedset-cookieMo"
"ndayTuesdayWednesdayThursdayFridaySaturdaySundayJanFebMarAprMayJunJulAugSe"
"pOctNovDecchunkedtext/htmlimage/pngimage/jpgimage/gifapplication/xmlapplic"
"ation/xhtmltext/plainpublicmax-agecharset=iso-8859-1utf-8gzipdeflateHTTP/1"
".1statusversionurl";
// use for zlib data types
void *
SpdyStream3::zlib_allocator(void *opaque, uInt items, uInt size)
{
return moz_xmalloc(items * size);
}
// use for zlib data types
void
SpdyStream3::zlib_destructor(void *opaque, void *addr)
{
moz_free(addr);
}
void
SpdyStream3::ExecuteCompress(PRUint32 flushMode)
{
// Expect mZlib->avail_in and mZlib->next_in to be set.
// Append the compressed version of next_in to mTxInlineFrame
do
{
PRUint32 avail = mTxInlineFrameSize - mTxInlineFrameUsed;
if (avail < 1) {
SpdySession3::EnsureBuffer(mTxInlineFrame,
mTxInlineFrameSize + 2000,
mTxInlineFrameUsed,
mTxInlineFrameSize);
avail = mTxInlineFrameSize - mTxInlineFrameUsed;
}
mZlib->next_out = reinterpret_cast<unsigned char *> (mTxInlineFrame.get()) +
mTxInlineFrameUsed;
mZlib->avail_out = avail;
deflate(mZlib, flushMode);
mTxInlineFrameUsed += avail - mZlib->avail_out;
} while (mZlib->avail_in > 0 || !mZlib->avail_out);
}
void
SpdyStream3::CompressToFrame(PRUint16 data)
{
// convert the data to network byte order and write that
// to the compressed stream
data = PR_htons(data);
mZlib->next_in = reinterpret_cast<unsigned char *> (&data);
mZlib->avail_in = 2;
ExecuteCompress(Z_NO_FLUSH);
}
void
SpdyStream3::CompressToFrame(const char *data, PRUint32 len)
{
// Format calls for a network ordered 16 bit length
// followed by the utf8 string
// for now, silently truncate headers greater than 64KB. Spdy/3 will
// fix this by making the len a 32 bit quantity
if (len > 0xffff)
len = 0xffff;
PRUint16 networkLen = PR_htons(len);
// write out the length
mZlib->next_in = reinterpret_cast<unsigned char *> (&networkLen);
mZlib->avail_in = 2;
ExecuteCompress(Z_NO_FLUSH);
// write out the data
mZlib->next_in = (unsigned char *)data;
mZlib->avail_in = len;
ExecuteCompress(Z_NO_FLUSH);
}
void
SpdyStream3::CompressFlushFrame()
{
mZlib->next_in = (unsigned char *) "";
mZlib->avail_in = 0;
ExecuteCompress(Z_SYNC_FLUSH);
}
void
SpdyStream3::Close(nsresult reason)
{
mTransaction->Close(reason);
}
//-----------------------------------------------------------------------------
// nsAHttpSegmentReader
//-----------------------------------------------------------------------------
nsresult
SpdyStream3::OnReadSegment(const char *buf,
PRUint32 count,
PRUint32 *countRead)
{
LOG3(("SpdyStream3::OnReadSegment %p count=%d state=%x",
this, count, mUpstreamState));
NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread");
NS_ABORT_IF_FALSE(mSegmentReader, "OnReadSegment with null mSegmentReader");
nsresult rv = NS_ERROR_UNEXPECTED;
PRUint32 dataLength;
switch (mUpstreamState) {
case GENERATING_SYN_STREAM:
// The buffer is the HTTP request stream, including at least part of the
// HTTP request header. This state's job is to build a SYN_STREAM frame
// from the header information. count is the number of http bytes available
// (which may include more than the header), and in countRead we return
// the number of those bytes that we consume (i.e. the portion that are
// header bytes)
rv = ParseHttpRequestHeaders(buf, count, countRead);
if (NS_FAILED(rv))
return rv;
LOG3(("ParseHttpRequestHeaders %p used %d of %d. complete = %d",
this, *countRead, count, mSynFrameComplete));
if (mSynFrameComplete) {
NS_ABORT_IF_FALSE(mTxInlineFrameUsed,
"OnReadSegment SynFrameComplete 0b");
rv = TransmitFrame(nsnull, nsnull);
NS_ABORT_IF_FALSE(NS_FAILED(rv) || !mTxInlineFrameUsed,
"Transmit Frame should be all or nothing");
// normalize a blocked write into an ok one if we have consumed the data
// while parsing headers as some code will take WOULD_BLOCK to mean an
// error with nothing processed.
// (e.g. nsHttpTransaction::ReadRequestSegment())
if (rv == NS_BASE_STREAM_WOULD_BLOCK && *countRead)
rv = NS_OK;
// mTxInlineFrameUsed > 0 means the current frame is in progress
// of sending. mTxInlineFrameUsed is dropped to 0 after both the frame
// and its payload (if any) are completely sent out. Here during
// GENERATING_SYN_STREAM state we are sending just the http headers.
// Only when the frame is completely sent out do we proceed to
// GENERATING_REQUEST_BODY state.
if (mTxInlineFrameUsed)
ChangeState(SENDING_SYN_STREAM);
else
ChangeState(GENERATING_REQUEST_BODY);
break;
}
NS_ABORT_IF_FALSE(*countRead == count,
"Header parsing not complete but unused data");
break;
case GENERATING_REQUEST_BODY:
dataLength = NS_MIN(count, mChunkSize);
LOG3(("SpdyStream3 %p id %x request len remaining %d, "
"count avail %d, chunk used %d",
this, mStreamID, mRequestBodyLenRemaining, count, dataLength));
if (dataLength > mRequestBodyLenRemaining)
return NS_ERROR_UNEXPECTED;
mRequestBodyLenRemaining -= dataLength;
GenerateDataFrameHeader(dataLength, !mRequestBodyLenRemaining);
ChangeState(SENDING_REQUEST_BODY);
// NO BREAK
case SENDING_REQUEST_BODY:
NS_ABORT_IF_FALSE(mTxInlineFrameUsed, "OnReadSegment Send Data Header 0b");
rv = TransmitFrame(buf, countRead);
NS_ABORT_IF_FALSE(NS_FAILED(rv) || !mTxInlineFrameUsed,
"Transmit Frame should be all or nothing");
LOG3(("TransmitFrame() rv=%x returning %d data bytes. "
"Header is %d Body is %d.",
rv, *countRead, mTxInlineFrameUsed, mTxStreamFrameSize));
// normalize a partial write with a WOULD_BLOCK into just a partial write
// as some code will take WOULD_BLOCK to mean an error with nothing
// written (e.g. nsHttpTransaction::ReadRequestSegment()
if (rv == NS_BASE_STREAM_WOULD_BLOCK && *countRead)
rv = NS_OK;
// If that frame was all sent, look for another one
if (!mTxInlineFrameUsed)
ChangeState(GENERATING_REQUEST_BODY);
break;
case SENDING_SYN_STREAM:
rv = NS_BASE_STREAM_WOULD_BLOCK;
break;
case SENDING_FIN_STREAM:
NS_ABORT_IF_FALSE(false,
"resuming partial fin stream out of OnReadSegment");
break;
default:
NS_ABORT_IF_FALSE(false, "SpdyStream3::OnReadSegment non-write state");
break;
}
return rv;
}
//-----------------------------------------------------------------------------
// nsAHttpSegmentWriter
//-----------------------------------------------------------------------------
nsresult
SpdyStream3::OnWriteSegment(char *buf,
PRUint32 count,
PRUint32 *countWritten)
{
LOG3(("SpdyStream3::OnWriteSegment %p count=%d state=%x",
this, count, mUpstreamState));
NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread");
NS_ABORT_IF_FALSE(mSegmentWriter, "OnWriteSegment with null mSegmentWriter");
return mSegmentWriter->OnWriteSegment(buf, count, countWritten);
}
} // namespace mozilla::net
} // namespace mozilla

View File

@ -0,0 +1,212 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set sw=2 ts=8 et tw=80 : */
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Mozilla.
*
* The Initial Developer of the Original Code is
* Mozilla Foundation.
* Portions created by the Initial Developer are Copyright (C) 2012
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Patrick McManus <mcmanus@ducksong.com>
*
* Alternatively, the contents of this file may be used under the terms of
* either of the GNU General Public License Version 2 or later (the "GPL"),
* or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
#ifndef mozilla_net_SpdyStream3_h
#define mozilla_net_SpdyStream3_h
#include "nsAHttpTransaction.h"
namespace mozilla { namespace net {
class SpdyStream3 : public nsAHttpSegmentReader
, public nsAHttpSegmentWriter
{
public:
NS_DECL_NSAHTTPSEGMENTREADER
NS_DECL_NSAHTTPSEGMENTWRITER
SpdyStream3(nsAHttpTransaction *,
SpdySession3 *, nsISocketTransport *,
PRUint32, z_stream *, PRInt32);
PRUint32 StreamID() { return mStreamID; }
nsresult ReadSegments(nsAHttpSegmentReader *, PRUint32, PRUint32 *);
nsresult WriteSegments(nsAHttpSegmentWriter *, PRUint32, PRUint32 *);
bool RequestBlockedOnRead()
{
return static_cast<bool>(mRequestBlockedOnRead);
}
// returns false if called more than once
bool GetFullyOpen() {return mFullyOpen;}
void SetFullyOpen()
{
NS_ABORT_IF_FALSE(!mFullyOpen, "SetFullyOpen already open");
mFullyOpen = 1;
}
nsAHttpTransaction *Transaction()
{
return mTransaction;
}
void Close(nsresult reason);
void SetRecvdFin(bool aStatus) { mRecvdFin = aStatus ? 1 : 0; }
bool RecvdFin() { return mRecvdFin; }
void UpdateTransportSendEvents(PRUint32 count);
void UpdateTransportReadEvents(PRUint32 count);
// The zlib header compression dictionary defined by SPDY,
// and hooks to the mozilla allocator for zlib to use.
static const char *kDictionary;
static void *zlib_allocator(void *, uInt, uInt);
static void zlib_destructor(void *, void *);
private:
// a SpdyStream3 object is only destroyed by being removed from the
// SpdySession3 mStreamTransactionHash - make the dtor private to
// just the AutoPtr implementation needed for that hash.
friend class nsAutoPtr<SpdyStream3>;
~SpdyStream3();
enum stateType {
GENERATING_SYN_STREAM,
SENDING_SYN_STREAM,
GENERATING_REQUEST_BODY,
SENDING_REQUEST_BODY,
SENDING_FIN_STREAM,
UPSTREAM_COMPLETE
};
static PLDHashOperator hdrHashEnumerate(const nsACString &,
nsAutoPtr<nsCString> &,
void *);
void ChangeState(enum stateType);
nsresult ParseHttpRequestHeaders(const char *, PRUint32, PRUint32 *);
nsresult TransmitFrame(const char *, PRUint32 *);
void GenerateDataFrameHeader(PRUint32, bool);
void CompressToFrame(const nsACString &);
void CompressToFrame(const nsACString *);
void CompressToFrame(const char *, PRUint32);
void CompressToFrame(PRUint16);
void CompressFlushFrame();
void ExecuteCompress(PRUint32);
// Each stream goes from syn_stream to upstream_complete, perhaps
// looping on multiple instances of generating_request_body and
// sending_request_body for each SPDY chunk in the upload.
enum stateType mUpstreamState;
// The underlying HTTP transaction. This pointer is used as the key
// in the SpdySession3 mStreamTransactionHash so it is important to
// keep a reference to it as long as this stream is a member of that hash.
// (i.e. don't change it or release it after it is set in the ctor).
nsRefPtr<nsAHttpTransaction> mTransaction;
// The session that this stream is a subset of
SpdySession3 *mSession;
// The underlying socket transport object is needed to propogate some events
nsISocketTransport *mSocketTransport;
// These are temporary state variables to hold the argument to
// Read/WriteSegments so it can be accessed by On(read/write)segment
// further up the stack.
nsAHttpSegmentReader *mSegmentReader;
nsAHttpSegmentWriter *mSegmentWriter;
// The 24 bit SPDY stream ID
PRUint32 mStreamID;
// The quanta upstream data frames are chopped into
PRUint32 mChunkSize;
// Flag is set when all http request headers have been read
PRUint32 mSynFrameComplete : 1;
// Flag is set when the HTTP processor has more data to send
// but has blocked in doing so.
PRUint32 mRequestBlockedOnRead : 1;
// Flag is set when a FIN has been placed on a data or syn packet
// (i.e after the client has closed)
PRUint32 mSentFinOnData : 1;
// Flag is set after the response frame bearing the fin bit has
// been processed. (i.e. after the server has closed).
PRUint32 mRecvdFin : 1;
// Flag is set after syn reply received
PRUint32 mFullyOpen : 1;
// Flag is set after the WAITING_FOR Transport event has been generated
PRUint32 mSentWaitingFor : 1;
// The InlineFrame and associated data is used for composing control
// frames and data frame headers.
nsAutoArrayPtr<char> mTxInlineFrame;
PRUint32 mTxInlineFrameSize;
PRUint32 mTxInlineFrameUsed;
// mTxStreamFrameSize tracks the progress of
// transmitting a request body data frame. The data frame itself
// is never copied into the spdy layer.
PRUint32 mTxStreamFrameSize;
// Compression context and buffer for request header compression.
// This is a copy of SpdySession3::mUpstreamZlib because it needs
// to remain the same in all streams of a session.
z_stream *mZlib;
nsCString mFlatHttpRequestHeaders;
// Track the content-length of a request body so that we can
// place the fin flag on the last data packet instead of waiting
// for a stream closed indication. Relying on stream close results
// in an extra 0-length runt packet and seems to have some interop
// problems with the google servers.
PRInt64 mRequestBodyLenRemaining;
// based on nsISupportsPriority definitions
PRInt32 mPriority;
// For Progress Events
PRUint64 mTotalSent;
PRUint64 mTotalRead;
};
}} // namespace mozilla::net
#endif // mozilla_net_SpdyStream3_h

View File

@ -169,6 +169,8 @@ nsHttpHandler::nsHttpHandler()
, mTelemetryEnabled(false)
, mAllowExperiments(true)
, mEnableSpdy(false)
, mSpdyV2(true)
, mSpdyV3(true)
, mCoalesceSpdy(true)
, mUseAlternateProtocol(false)
, mSpdySendingChunkSize(ASpdySession::kSendingChunkSize)
@ -1132,6 +1134,18 @@ nsHttpHandler::PrefsChanged(nsIPrefBranch *prefs, const char *pref)
mEnableSpdy = cVar;
}
if (PREF_CHANGED(HTTP_PREF("spdy.enabled.v2"))) {
rv = prefs->GetBoolPref(HTTP_PREF("spdy.enabled.v2"), &cVar);
if (NS_SUCCEEDED(rv))
mSpdyV2 = cVar;
}
if (PREF_CHANGED(HTTP_PREF("spdy.enabled.v3"))) {
rv = prefs->GetBoolPref(HTTP_PREF("spdy.enabled.v3"), &cVar);
if (NS_SUCCEEDED(rv))
mSpdyV3 = cVar;
}
if (PREF_CHANGED(HTTP_PREF("spdy.coalesce-hostnames"))) {
rv = prefs->GetBoolPref(HTTP_PREF("spdy.coalesce-hostnames"), &cVar);
if (NS_SUCCEEDED(rv))

View File

@ -87,6 +87,8 @@ public:
bool AllowExperiments() { return mTelemetryEnabled && mAllowExperiments; }
bool IsSpdyEnabled() { return mEnableSpdy; }
bool IsSpdyV2Enabled() { return mSpdyV2; }
bool IsSpdyV3Enabled() { return mSpdyV3; }
bool CoalesceSpdy() { return mCoalesceSpdy; }
bool UseAlternateProtocol() { return mUseAlternateProtocol; }
PRUint32 SpdySendingChunkSize() { return mSpdySendingChunkSize; }
@ -357,6 +359,8 @@ private:
// Try to use SPDY features instead of HTTP/1.1 over SSL
mozilla::net::SpdyInformation mSpdyInfo;
bool mEnableSpdy;
bool mSpdyV2;
bool mSpdyV3;
bool mCoalesceSpdy;
bool mUseAlternateProtocol;
PRUint32 mSpdySendingChunkSize;