From 122f1fecac99987dfad3201d369abd41801e27d9 Mon Sep 17 00:00:00 2001 From: Jason Duell Date: Thu, 28 Mar 2013 15:52:16 -0700 Subject: [PATCH] Bug 849364 - Provide per-websocket way to enable keepalive pings. r=mcmanus --- .../websocket/BaseWebSocketChannel.cpp | 47 ++++++++++++++++- .../protocol/websocket/BaseWebSocketChannel.h | 13 ++++- netwerk/protocol/websocket/PWebSocket.ipdl | 7 ++- .../protocol/websocket/WebSocketChannel.cpp | 52 +++++++++---------- netwerk/protocol/websocket/WebSocketChannel.h | 10 ++-- .../websocket/WebSocketChannelChild.cpp | 5 +- .../websocket/WebSocketChannelParent.cpp | 14 ++++- .../websocket/WebSocketChannelParent.h | 6 ++- .../websocket/nsIWebSocketChannel.idl | 22 ++++++++ 9 files changed, 140 insertions(+), 36 deletions(-) diff --git a/netwerk/protocol/websocket/BaseWebSocketChannel.cpp b/netwerk/protocol/websocket/BaseWebSocketChannel.cpp index 7db955aa036e..346d0d5831c3 100644 --- a/netwerk/protocol/websocket/BaseWebSocketChannel.cpp +++ b/netwerk/protocol/websocket/BaseWebSocketChannel.cpp @@ -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 //----------------------------------------------------------------------------- diff --git a/netwerk/protocol/websocket/BaseWebSocketChannel.h b/netwerk/protocol/websocket/BaseWebSocketChannel.h index cafc8a33b875..1fbc0923c2c1 100644 --- a/netwerk/protocol/websocket/BaseWebSocketChannel.h +++ b/netwerk/protocol/websocket/BaseWebSocketChannel.h @@ -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 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 diff --git a/netwerk/protocol/websocket/PWebSocket.ipdl b/netwerk/protocol/websocket/PWebSocket.ipdl index 9c111eab4592..68585d0fb93c 100644 --- a/netwerk/protocol/websocket/PWebSocket.ipdl +++ b/netwerk/protocol/websocket/PWebSocket.ipdl @@ -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); diff --git a/netwerk/protocol/websocket/WebSocketChannel.cpp b/netwerk/protocol/websocket/WebSocketChannel.cpp index 2a1deb9369ac..a6e232eafd12 100644 --- a/netwerk/protocol/websocket/WebSocketChannel.cpp +++ b/netwerk/protocol/websocket/WebSocketChannel.cpp @@ -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, diff --git a/netwerk/protocol/websocket/WebSocketChannel.h b/netwerk/protocol/websocket/WebSocketChannel.h index 453662735844..d2f2a148e3a5 100644 --- a/netwerk/protocol/websocket/WebSocketChannel.h +++ b/netwerk/protocol/websocket/WebSocketChannel.h @@ -151,6 +151,13 @@ private: uint32_t accumulatedFragments, uint32_t *available); + inline void ResetPingTimer() + { + if (mPingTimer) { + mPingOutstanding = 0; + mPingTimer->SetDelay(mPingInterval); + } + } nsCOMPtr mSocketThread; nsCOMPtr mChannel; @@ -179,8 +186,6 @@ private: nsCOMPtr mReconnectDelayTimer; nsCOMPtr mPingTimer; - uint32_t mPingTimeout; /* milliseconds */ - uint32_t mPingResponseTimeout; /* milliseconds */ nsCOMPtr 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; diff --git a/netwerk/protocol/websocket/WebSocketChannelChild.cpp b/netwerk/protocol/websocket/WebSocketChannelChild.cpp index e2b8e4fdf799..620d2adef5cf 100644 --- a/netwerk/protocol/websocket/WebSocketChannelChild.cpp +++ b/netwerk/protocol/websocket/WebSocketChannelChild.cpp @@ -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; } diff --git a/netwerk/protocol/websocket/WebSocketChannelParent.cpp b/netwerk/protocol/websocket/WebSocketChannelParent.cpp index 2edabddce278..176171d27e64 100644 --- a/netwerk/protocol/websocket/WebSocketChannelParent.cpp +++ b/netwerk/protocol/websocket/WebSocketChannelParent.cpp @@ -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; diff --git a/netwerk/protocol/websocket/WebSocketChannelParent.h b/netwerk/protocol/websocket/WebSocketChannelParent.h index 760b2975dd76..345691716ad3 100644 --- a/netwerk/protocol/websocket/WebSocketChannelParent.h +++ b/netwerk/protocol/websocket/WebSocketChannelParent.h @@ -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); diff --git a/netwerk/protocol/websocket/nsIWebSocketChannel.idl b/netwerk/protocol/websocket/nsIWebSocketChannel.idl index 638b374cb86c..5b700efa9521 100644 --- a/netwerk/protocol/websocket/nsIWebSocketChannel.idl +++ b/netwerk/protocol/websocket/nsIWebSocketChannel.idl @@ -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; + };