Bug 849364 - Provide per-websocket way to enable keepalive pings. r=mcmanus

This commit is contained in:
Jason Duell 2013-03-28 15:52:16 -07:00
parent 99acd93e07
commit 122f1fecac
9 changed files with 140 additions and 36 deletions

View File

@ -20,7 +20,12 @@ namespace mozilla {
namespace net {
BaseWebSocketChannel::BaseWebSocketChannel()
: mEncrypted(false)
: mEncrypted(0)
, mWasOpened(0)
, mClientSetPingInterval(0)
, mClientSetPingTimeout(0)
, mPingInterval(0)
, mPingResponseTimeout(10000)
{
#if defined(PR_LOGGING)
if (!webSocketLog)
@ -115,6 +120,46 @@ BaseWebSocketChannel::SetProtocol(const nsACString &aProtocol)
return NS_OK;
}
NS_IMETHODIMP
BaseWebSocketChannel::GetPingInterval(uint32_t *aMilliSeconds)
{
*aMilliSeconds = mPingInterval;
return NS_OK;
}
NS_IMETHODIMP
BaseWebSocketChannel::SetPingInterval(uint32_t aMilliSeconds)
{
if (mWasOpened) {
return NS_ERROR_IN_PROGRESS;
}
mPingInterval = aMilliSeconds;
mClientSetPingInterval = 1;
return NS_OK;
}
NS_IMETHODIMP
BaseWebSocketChannel::GetPingTimeout(uint32_t *aMilliSeconds)
{
*aMilliSeconds = mPingResponseTimeout;
return NS_OK;
}
NS_IMETHODIMP
BaseWebSocketChannel::SetPingTimeout(uint32_t aMilliSeconds)
{
if (mWasOpened) {
return NS_ERROR_IN_PROGRESS;
}
mPingResponseTimeout = aMilliSeconds;
mClientSetPingTimeout = 1;
return NS_OK;
}
//-----------------------------------------------------------------------------
// BaseWebSocketChannel::nsIProtocolHandler
//-----------------------------------------------------------------------------

View File

@ -42,6 +42,10 @@ class BaseWebSocketChannel : public nsIWebSocketChannel,
NS_IMETHOD GetExtensions(nsACString &aExtensions);
NS_IMETHOD GetProtocol(nsACString &aProtocol);
NS_IMETHOD SetProtocol(const nsACString &aProtocol);
NS_IMETHOD GetPingInterval(uint32_t *aMilliSeconds);
NS_IMETHOD SetPingInterval(uint32_t aMilliSeconds);
NS_IMETHOD GetPingTimeout(uint32_t *aMilliSeconds);
NS_IMETHOD SetPingTimeout(uint32_t aMilliSeconds);
protected:
nsCOMPtr<nsIURI> mOriginalURI;
@ -54,8 +58,15 @@ class BaseWebSocketChannel : public nsIWebSocketChannel,
nsCString mProtocol;
nsCString mOrigin;
bool mEncrypted;
nsCString mNegotiatedExtensions;
uint32_t mEncrypted : 1;
uint32_t mWasOpened : 1;
uint32_t mClientSetPingInterval : 1;
uint32_t mClientSetPingTimeout : 1;
uint32_t mPingInterval; /* milliseconds */
uint32_t mPingResponseTimeout; /* milliseconds */
};
} // namespace net

View File

@ -27,7 +27,12 @@ parent:
AsyncOpen(URIParams aURI,
nsCString aOrigin,
nsCString aProtocol,
bool aSecure);
bool aSecure,
// ping values only meaningful if client set them
uint32_t aPingInterval,
bool aClientSetPingInterval,
uint32_t aPingTimeout,
bool aClientSetPingTimeout);
Close(uint16_t code, nsCString reason);
SendMsg(nsCString aMsg);
SendBinaryMsg(nsCString aMsg);

View File

@ -921,8 +921,6 @@ WebSocketChannel::WebSocketChannel() :
mCloseTimeout(20000),
mOpenTimeout(20000),
mConnecting(NOT_CONNECTING),
mPingTimeout(0),
mPingResponseTimeout(10000),
mMaxConcurrentConnections(200),
mGotUpgradeOK(0),
mRecvdHttpUpgradeTransport(0),
@ -936,7 +934,6 @@ WebSocketChannel::WebSocketChannel() :
mAutoFollowRedirects(0),
mReleaseOnTransmit(0),
mTCPClosed(0),
mWasOpened(0),
mOpenedHttpChannel(0),
mDataStarted(0),
mIncrementedSessionCount(0),
@ -1153,15 +1150,11 @@ WebSocketChannel::ProcessInput(uint8_t *buffer, uint32_t count)
LOG(("WebSocketChannel::ProcessInput %p [%d %d]\n", this, count, mBuffered));
NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "not socket thread");
// reset the ping timer
if (mPingTimer) {
// The purpose of ping/pong is to actively probe the peer so that an
// unreachable peer is not mistaken for a period of idleness. This
// implementation accepts any application level read activity as a sign of
// life, it does not necessarily have to be a pong.
mPingOutstanding = 0;
mPingTimer->SetDelay(mPingTimeout);
}
// The purpose of ping/pong is to actively probe the peer so that an
// unreachable peer is not mistaken for a period of idleness. This
// implementation accepts any application level read activity as a sign of
// life, it does not necessarily have to be a pong.
ResetPingTimer();
uint32_t avail;
@ -2233,6 +2226,20 @@ WebSocketChannel::StartWebsocketData()
if (mListener)
mListener->OnStart(mContext);
// Start keepalive ping timer, if we're using keepalive.
if (mPingInterval) {
nsresult rv;
mPingTimer = do_CreateInstance("@mozilla.org/timer;1", &rv);
if (NS_FAILED(rv)) {
NS_WARNING("unable to create ping timer. Carrying on.");
} else {
LOG(("WebSocketChannel will generate ping after %d ms of receive silence\n",
mPingInterval));
mPingTimer->SetTarget(mSocketThread);
mPingTimer->InitWithCallback(this, mPingInterval, nsITimer::TYPE_ONE_SHOT);
}
}
return mSocketIn->AsyncWait(this, 0, 0, mSocketThread);
}
@ -2507,6 +2514,7 @@ WebSocketChannel::Notify(nsITimer *timer)
return NS_OK;
}
// nsIWebSocketChannel
NS_IMETHODIMP
WebSocketChannel::GetSecurityInfo(nsISupports **aSecurityInfo)
@ -2576,12 +2584,12 @@ WebSocketChannel::AsyncOpen(nsIURI *aURI,
}
rv = prefService->GetIntPref("network.websocket.timeout.ping.request",
&intpref);
if (NS_SUCCEEDED(rv)) {
mPingTimeout = clamped(intpref, 0, 86400) * 1000;
if (NS_SUCCEEDED(rv) && !mClientSetPingInterval) {
mPingInterval = clamped(intpref, 0, 86400) * 1000;
}
rv = prefService->GetIntPref("network.websocket.timeout.ping.response",
&intpref);
if (NS_SUCCEEDED(rv)) {
if (NS_SUCCEEDED(rv) && !mClientSetPingTimeout) {
mPingResponseTimeout = clamped(intpref, 1, 3600) * 1000;
}
rv = prefService->GetBoolPref("network.websocket.extensions.stream-deflate",
@ -2617,18 +2625,6 @@ WebSocketChannel::AsyncOpen(nsIURI *aURI,
return NS_ERROR_SOCKET_CREATE_FAILED;
}
if (mPingTimeout) {
mPingTimer = do_CreateInstance("@mozilla.org/timer;1", &rv);
if (NS_FAILED(rv)) {
NS_WARNING("unable to create ping timer. Carrying on.");
} else {
LOG(("WebSocketChannel will generate ping after %d ms of receive silence\n",
mPingTimeout));
mPingTimer->SetTarget(mSocketThread);
mPingTimer->InitWithCallback(this, mPingTimeout, nsITimer::TYPE_ONE_SHOT);
}
}
mOriginalURI = aURI;
mURI = mOriginalURI;
mOrigin = aOrigin;
@ -2801,6 +2797,8 @@ WebSocketChannel::SendMsgCommon(const nsACString *aMsg, bool aIsBinary,
nsIEventTarget::DISPATCH_NORMAL);
}
// nsIHttpUpgradeListener
NS_IMETHODIMP
WebSocketChannel::OnTransportAvailable(nsISocketTransport *aTransport,
nsIAsyncInputStream *aSocketIn,

View File

@ -151,6 +151,13 @@ private:
uint32_t accumulatedFragments,
uint32_t *available);
inline void ResetPingTimer()
{
if (mPingTimer) {
mPingOutstanding = 0;
mPingTimer->SetDelay(mPingInterval);
}
}
nsCOMPtr<nsIEventTarget> mSocketThread;
nsCOMPtr<nsIHttpChannelInternal> mChannel;
@ -179,8 +186,6 @@ private:
nsCOMPtr<nsITimer> mReconnectDelayTimer;
nsCOMPtr<nsITimer> mPingTimer;
uint32_t mPingTimeout; /* milliseconds */
uint32_t mPingResponseTimeout; /* milliseconds */
nsCOMPtr<nsITimer> mLingeringCloseTimer;
const static int32_t kLingeringCloseTimeout = 1000;
@ -200,7 +205,6 @@ private:
uint32_t mAutoFollowRedirects : 1;
uint32_t mReleaseOnTransmit : 1;
uint32_t mTCPClosed : 1;
uint32_t mWasOpened : 1;
uint32_t mOpenedHttpChannel : 1;
uint32_t mDataStarted : 1;
uint32_t mIncrementedSessionCount : 1;

View File

@ -344,7 +344,9 @@ WebSocketChannelChild::AsyncOpen(nsIURI *aURI,
gNeckoChild->SendPWebSocketConstructor(this, tabChild,
IPC::SerializedLoadContext(this));
if (!SendAsyncOpen(uri, nsCString(aOrigin), mProtocol, mEncrypted))
if (!SendAsyncOpen(uri, nsCString(aOrigin), mProtocol, mEncrypted,
mPingInterval, mClientSetPingInterval,
mPingResponseTimeout, mClientSetPingTimeout))
return NS_ERROR_UNEXPECTED;
mOriginalURI = aURI;
@ -352,6 +354,7 @@ WebSocketChannelChild::AsyncOpen(nsIURI *aURI,
mListener = aListener;
mContext = aContext;
mOrigin = aOrigin;
mWasOpened = 1;
return NS_OK;
}

View File

@ -51,7 +51,11 @@ bool
WebSocketChannelParent::RecvAsyncOpen(const URIParams& aURI,
const nsCString& aOrigin,
const nsCString& aProtocol,
const bool& aSecure)
const bool& aSecure,
const uint32_t& aPingInterval,
const bool& aClientSetPingInterval,
const uint32_t& aPingTimeout,
const bool& aClientSetPingTimeout)
{
LOG(("WebSocketChannelParent::RecvAsyncOpen() %p\n", this));
@ -82,6 +86,14 @@ WebSocketChannelParent::RecvAsyncOpen(const URIParams& aURI,
goto fail;
}
// only use ping values from child if they were overridden by client code.
if (aClientSetPingInterval) {
mChannel->SetPingInterval(aPingInterval);
}
if (aClientSetPingTimeout) {
mChannel->SetPingTimeout(aPingTimeout);
}
rv = mChannel->AsyncOpen(uri, aOrigin, this, nullptr);
if (NS_FAILED(rv))
goto fail;

View File

@ -38,7 +38,11 @@ class WebSocketChannelParent : public PWebSocketParent,
bool RecvAsyncOpen(const URIParams& aURI,
const nsCString& aOrigin,
const nsCString& aProtocol,
const bool& aSecure);
const bool& aSecure,
const uint32_t& aPingInterval,
const bool& aClientSetPingInterval,
const uint32_t& aPingTimeout,
const bool& aClientSetPingTimeout);
bool RecvClose(const uint16_t & code, const nsCString & reason);
bool RecvSendMsg(const nsCString& aMsg);
bool RecvSendBinaryMsg(const nsCString& aMsg);

View File

@ -133,4 +133,26 @@ interface nsIWebSocketChannel : nsISupports
*/
void sendBinaryStream(in nsIInputStream aStream,
in unsigned long length);
/**
* This value determines how often (in milliseconds) websocket keepalive
* pings are sent. If set to 0 (the default), no pings are ever sent.
*
* This value can currently only be set before asyncOpen is called, else
* NS_ERROR_IN_PROGRESS is thrown.
*
* Be careful using this setting: ping traffic can consume lots of power and
* bandwidth over time.
*/
attribute unsigned long pingInterval;
/**
* This value determines how long (in milliseconds) the websocket waits for
* the server to reply to a ping that has been sent.
*
* This value can currently only be set before asyncOpen is called, else
* NS_ERROR_IN_PROGRESS is thrown.
*/
attribute unsigned long pingTimeout;
};