Bug 572975, bug 573227, websocket fixes, r=smaug

This commit is contained in:
2010-06-28 00:09:29 +03:00
parent 79d5b2b321
commit dd9d806613
5 changed files with 488 additions and 152 deletions

View File

@@ -102,7 +102,7 @@ static nsIThread *gWebSocketThread = nsnull;
#define TIMEOUT_TRY_CONNECT_AGAIN 1000
#define TIMEOUT_WAIT_FOR_SERVER_RESPONSE 20000
#define TIMEOUT_WAIT_FOR_CLOSING 5000
#define TIMEOUT_WAIT_FOR_CLOSING 20000
#define ENSURE_TRUE_AND_FAIL_IF_FAILED(x, ret) \
PR_BEGIN_MACRO \
@@ -240,7 +240,10 @@ public:
nsresult Init(nsWebSocket *aOwner);
nsresult Disconnect();
// these are called always on the main thread (they dispatch themselves)
// These are called always on the main thread (they dispatch themselves).
// ATTENTION, this method when called can release both the WebSocket object
// (i.e. mOwner) and its connection (i.e. *this*) if there are no strong event
// listeners.
DECL_RUNNABLE_ON_MAIN_THREAD_METHOD(Close)
DECL_RUNNABLE_ON_MAIN_THREAD_METHOD(FailConnection)
@@ -253,6 +256,17 @@ public:
static nsTArray<nsRefPtr<nsWebSocketEstablishedConnection> >* sWSsConnecting;
private:
enum WSFrameType {
eConnectFrame,
eUTF8MessageFrame,
eCloseFrame
};
struct nsWSFrame {
WSFrameType mType;
nsAutoPtr<nsCString> mData;
};
// We can only establish one connection at a time per IP address.
// TryConnect ensures this by checking sWSsConnecting.
// If there is a IP address entry there it tries again after
@@ -291,7 +305,8 @@ private:
nsresult Reset();
void RemoveFromLoadGroup();
nsresult ProcessHeaders();
nsresult PostData(nsCString *aBuffer, PRBool aIsMessage);
nsresult PostData(nsCString *aBuffer,
WSFrameType aWSFrameType);
nsresult PrintErrorOnConsole(const char *aBundleURI,
const PRUnichar *aError,
const PRUnichar **aFormatStrings,
@@ -324,7 +339,7 @@ private:
nsCOMPtr<nsIAsyncInputStream> mSocketInput;
nsCOMPtr<nsIAsyncOutputStream> mSocketOutput;
nsCOMPtr<nsIProxyInfo> mProxyInfo;
nsDeque mOutgoingMessages; // has nsCString* which need to be sent
nsDeque mOutgoingMessages; // has nsWSFrame* which need to be sent
PRUint32 mBytesAlreadySentOfFirstOutString;
PRUint32 mOutgoingBufferedAmount; // not really necessary, but it is
// here for fast access.
@@ -652,14 +667,17 @@ nsWebSocketEstablishedConnection::nsWebSocketEstablishedConnection() :
nsWebSocketEstablishedConnection::~nsWebSocketEstablishedConnection()
{
NS_ASSERTION(!mOwner, "Disconnect wasn't called!");
}
nsresult
nsWebSocketEstablishedConnection::PostData(nsCString *aBuffer,
PRBool aIsMessage)
WSFrameType aWSFrameType)
{
NS_ASSERTION(NS_IsMainThread(), "Not running on main thread");
nsAutoPtr<nsCString> data(aBuffer);
if (mStatus == CONN_CLOSED) {
NS_ASSERTION(mOwner, "Posting data after disconnecting the websocket!");
// the tcp connection has been closed, but the main thread hasn't received
@@ -669,14 +687,21 @@ nsWebSocketEstablishedConnection::PostData(nsCString *aBuffer,
MutexAutoLock lockOut(mLockOutgoingMessages);
nsAutoPtr<nsWSFrame> frame(new nsWSFrame());
NS_ENSURE_TRUE(frame.get(), NS_ERROR_OUT_OF_MEMORY);
frame->mType = aWSFrameType;
frame->mData = data.forget();
nsresult rv;
PRInt32 sizeBefore = mOutgoingMessages.GetSize();
mOutgoingMessages.Push(aBuffer);
mOutgoingMessages.Push(frame.forget());
NS_ENSURE_TRUE(mOutgoingMessages.GetSize() == sizeBefore + 1,
NS_ERROR_OUT_OF_MEMORY);
if (aIsMessage) {
if (aWSFrameType == eUTF8MessageFrame) {
// without the START_BYTE_OF_MESSAGE and END_BYTE_OF_MESSAGE bytes
mOutgoingBufferedAmount += aBuffer->Length() - 2;
} else if (aWSFrameType == eCloseFrame) {
mPostedCloseFrame = PR_TRUE;
}
if (sizeBefore == 0) {
@@ -752,7 +777,7 @@ nsWebSocketEstablishedConnection::PostMessage(const nsString& aMessage)
ENSURE_TRUE_AND_FAIL_IF_FAILED(buf->Length() == static_cast<PRUint32>(outLen),
NS_ERROR_UNEXPECTED);
rv = PostData(buf.forget(), PR_TRUE);
rv = PostData(buf.forget(), eUTF8MessageFrame);
ENSURE_SUCCESS_AND_FAIL_IF_FAILED(rv, rv);
return NS_OK;
@@ -873,7 +898,7 @@ IMPL_RUNNABLE_ON_MAIN_THREAD_METHOD_BEGIN(DoInitialRequest)
mStatus = CONN_SENDING_INITIAL_REQUEST;
rv = PostData(buf.forget(), PR_FALSE);
rv = PostData(buf.forget(), eConnectFrame);
CHECK_SUCCESS_AND_FAIL_IF_FAILED(rv);
}
IMPL_RUNNABLE_ON_MAIN_THREAD_METHOD_END
@@ -1455,11 +1480,11 @@ nsWebSocketEstablishedConnection::Reset()
mSocketOutput = nsnull;
while (mOutgoingMessages.GetSize() != 0) {
delete static_cast<nsCString*>(mOutgoingMessages.PopFront());
delete static_cast<nsWSFrame*>(mOutgoingMessages.PopFront());
}
while (mReceivedMessages.GetSize() != 0) {
delete static_cast<nsString*>(mReceivedMessages.PopFront());
delete static_cast<nsCString*>(mReceivedMessages.PopFront());
}
mBytesAlreadySentOfFirstOutString = 0;
@@ -1685,7 +1710,7 @@ nsWebSocketEstablishedConnection::DoConnect()
mStatus = CONN_CONNECTING_TO_HTTP_PROXY;
rv = PostData(buf.forget(), PR_FALSE);
rv = PostData(buf.forget(), eConnectFrame);
ENSURE_SUCCESS_AND_FAIL_IF_FAILED(rv, rv);
return NS_OK;
@@ -1945,6 +1970,10 @@ IMPL_RUNNABLE_ON_MAIN_THREAD_METHOD_BEGIN(Close)
{
nsresult rv;
// Disconnect() can release this object, so we keep a
// reference until the end of the method
nsRefPtr<nsWebSocketEstablishedConnection> kungfuDeathGrip = this;
if (mOwner->mReadyState == nsIWebSocket::CONNECTING) {
// we must not convey any failure information to scripts, so we just
// disconnect and maintain the owner WebSocket object in the CONNECTING
@@ -1987,13 +2016,11 @@ IMPL_RUNNABLE_ON_MAIN_THREAD_METHOD_BEGIN(Close)
closeFrame->SetCharAt(START_BYTE_OF_CLOSE_FRAME, 0);
closeFrame->SetCharAt(END_BYTE_OF_CLOSE_FRAME, 1);
rv = PostData(closeFrame.forget(), PR_FALSE);
rv = PostData(closeFrame.forget(), eCloseFrame);
if (NS_FAILED(rv)) {
NS_WARNING("Failed to post the close frame");
return;
}
mPostedCloseFrame = PR_TRUE;
} else {
// Probably failed to send the close frame. Just disconnect.
Disconnect();
@@ -2004,6 +2031,10 @@ IMPL_RUNNABLE_ON_MAIN_THREAD_METHOD_END
void
nsWebSocketEstablishedConnection::ForceClose()
{
// Disconnect() can release this object, so we keep a
// reference until the end of the method
nsRefPtr<nsWebSocketEstablishedConnection> kungfuDeathGrip = this;
if (mOwner->mReadyState == nsIWebSocket::CONNECTING) {
// we must not convey any failure information to scripts, so we just
// disconnect and maintain the owner WebSocket object in the CONNECTING
@@ -2061,6 +2092,12 @@ nsWebSocketEstablishedConnection::Disconnect()
return NS_OK;
}
// If mOwner is deleted when calling mOwner->DontKeepAliveAnyMore()
// then this method can be called again, and we will get a deadlock.
nsRefPtr<nsWebSocket> kungfuDeathGrip = mOwner;
mOwner->DontKeepAliveAnyMore();
RemoveWSConnecting();
mStatus = CONN_CLOSED;
@@ -2111,11 +2148,11 @@ nsWebSocketEstablishedConnection::Disconnect()
mProxyInfo = nsnull;
while (mOutgoingMessages.GetSize() != 0) {
delete static_cast<nsCString*>(mOutgoingMessages.PopFront());
delete static_cast<nsWSFrame*>(mOutgoingMessages.PopFront());
}
while (mReceivedMessages.GetSize() != 0) {
delete static_cast<nsString*>(mReceivedMessages.PopFront());
delete static_cast<nsCString*>(mReceivedMessages.PopFront());
}
// Remove ourselves from the document's load group. nsIRequest expects
@@ -2606,13 +2643,13 @@ nsWebSocketEstablishedConnection::OnInputStreamReady(nsIAsyncInputStream *aStrea
// closed. In this case we have to reset the WebSocket, not Close it.
if (mStatus != CONN_RETRYING_TO_AUTHENTICATE) {
mStatus = CONN_CLOSED;
mFailureStatus = NS_BASE_STREAM_CLOSED;
if (mStatus < CONN_CONNECTED_AND_READY) {
FailConnection();
} else {
Close();
}
}
mFailureStatus = NS_BASE_STREAM_CLOSED;
return NS_BASE_STREAM_CLOSED;
}
@@ -2659,13 +2696,14 @@ nsWebSocketEstablishedConnection::OnOutputStreamReady(nsIAsyncOutputStream *aStr
// send what we can of the 1st string
nsCString *strToSend =
static_cast<nsCString*>(mOutgoingMessages.PeekFront());
nsWSFrame *frameToSend =
static_cast<nsWSFrame*>(mOutgoingMessages.PeekFront());
nsCString *strToSend = frameToSend->mData;
PRUint32 sizeToSend =
strToSend->Length() - mBytesAlreadySentOfFirstOutString;
PRBool currentStrHasStartFrameByte =
(mBytesAlreadySentOfFirstOutString == 0);
PRBool strIsMessage = (mStatus >= CONN_CONNECTED_AND_READY);
PRBool strIsMessage = (frameToSend->mType == eUTF8MessageFrame);
if (sizeToSend != 0) {
PRUint32 written;
@@ -2691,12 +2729,12 @@ nsWebSocketEstablishedConnection::OnOutputStreamReady(nsIAsyncOutputStream *aStr
if (written == 0) {
mStatus = CONN_CLOSED;
mFailureStatus = NS_BASE_STREAM_CLOSED;
if (mStatus < CONN_CONNECTED_AND_READY) {
FailConnection();
} else {
Close();
}
mFailureStatus = NS_BASE_STREAM_CLOSED;
return NS_BASE_STREAM_CLOSED;
}
@@ -2731,7 +2769,7 @@ nsWebSocketEstablishedConnection::OnOutputStreamReady(nsIAsyncOutputStream *aStr
// ok, send the next string
mOutgoingMessages.PopFront();
delete strToSend;
delete frameToSend;
mBytesAlreadySentOfFirstOutString = 0;
}
@@ -2803,13 +2841,19 @@ nsWebSocketEstablishedConnection::GetInterface(const nsIID &aIID,
// nsWebSocket
////////////////////////////////////////////////////////////////////////////////
nsWebSocket::nsWebSocket() : mReadyState(nsIWebSocket::CONNECTING),
nsWebSocket::nsWebSocket() : mHasStrongEventListeners(PR_FALSE),
mCheckThereAreStrongEventListeners(PR_TRUE),
mReadyState(nsIWebSocket::CONNECTING),
mOutgoingBufferedAmount(0)
{
}
nsWebSocket::~nsWebSocket()
{
if (mConnection) {
mConnection->Disconnect();
mConnection = nsnull;
}
if (mListenerManager) {
mListenerManager->Disconnect();
mListenerManager = nsnull;
@@ -2832,16 +2876,16 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(nsWebSocket,
nsDOMEventTargetWrapperCache)
if (tmp->mConnection) {
tmp->mConnection->Disconnect();
tmp->mConnection = nsnull;
}
NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mOnOpenListener)
NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mOnMessageListener)
NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mOnCloseListener)
NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mOnErrorListener)
NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mPrincipal)
NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mURI)
if (tmp->mConnection) {
tmp->mConnection->Disconnect();
tmp->mConnection = nsnull;
}
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
DOMCI_DATA(WebSocket, nsWebSocket)
@@ -2955,7 +2999,9 @@ public:
NS_IMETHOD Run()
{
return mWebSocket->CreateAndDispatchCloseEvent(mWasClean);
nsresult rv = mWebSocket->CreateAndDispatchCloseEvent(mWasClean);
mWebSocket->UpdateMustKeepAlive();
return rv;
}
private:
@@ -3073,6 +3119,7 @@ nsWebSocket::SetReadyState(PRUint16 aNewReadyState)
if (NS_FAILED(rv)) {
NS_WARNING("Failed to dispatch the open event");
}
UpdateMustKeepAlive();
return;
}
@@ -3099,6 +3146,7 @@ nsWebSocket::SetReadyState(PRUint16 aNewReadyState)
rv = NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
if (NS_FAILED(rv)) {
NS_WARNING("Failed to dispatch the close event");
UpdateMustKeepAlive();
}
}
}
@@ -3210,6 +3258,94 @@ nsWebSocket::SetProtocol(const nsString& aProtocol)
return NS_OK;
}
//-----------------------------------------------------------------------------
// Methods that keep alive the WebSocket object when there are
// onopen/onmessage event listeners.
//-----------------------------------------------------------------------------
void
nsWebSocket::UpdateMustKeepAlive()
{
if (!mCheckThereAreStrongEventListeners) {
return;
}
if (mHasStrongEventListeners) {
if (!mListenerManager ||
((mReadyState != nsIWebSocket::CONNECTING ||
!mListenerManager->HasListenersFor(NS_LITERAL_STRING("open"))) &&
(mReadyState == nsIWebSocket::CLOSED ||
!mListenerManager->HasListenersFor(NS_LITERAL_STRING("message"))))) {
mHasStrongEventListeners = PR_FALSE;
static_cast<nsPIDOMEventTarget*>(this)->Release();
}
} else {
if ((mReadyState == nsIWebSocket::CONNECTING && mListenerManager &&
mListenerManager->HasListenersFor(NS_LITERAL_STRING("open"))) ||
(mReadyState != nsIWebSocket::CLOSED && mListenerManager &&
mListenerManager->HasListenersFor(NS_LITERAL_STRING("message")))) {
mHasStrongEventListeners = PR_TRUE;
static_cast<nsPIDOMEventTarget*>(this)->AddRef();
}
}
}
void
nsWebSocket::DontKeepAliveAnyMore()
{
if (mHasStrongEventListeners) {
mCheckThereAreStrongEventListeners = PR_FALSE;
mHasStrongEventListeners = PR_FALSE;
static_cast<nsPIDOMEventTarget*>(this)->Release();
}
}
NS_IMETHODIMP
nsWebSocket::AddEventListener(const nsAString& aType,
nsIDOMEventListener* aListener,
PRBool aUseCapture)
{
nsresult rv = nsDOMEventTargetHelper::AddEventListener(aType,
aListener,
aUseCapture);
if (NS_SUCCEEDED(rv)) {
UpdateMustKeepAlive();
}
return rv;
}
NS_IMETHODIMP
nsWebSocket::RemoveEventListener(const nsAString& aType,
nsIDOMEventListener* aListener,
PRBool aUseCapture)
{
nsresult rv = nsDOMEventTargetHelper::RemoveEventListener(aType,
aListener,
aUseCapture);
if (NS_SUCCEEDED(rv)) {
UpdateMustKeepAlive();
}
return rv;
}
NS_IMETHODIMP
nsWebSocket::AddEventListener(const nsAString& aType,
nsIDOMEventListener *aListener,
PRBool aUseCapture,
PRBool aWantsUntrusted,
PRUint8 optional_argc)
{
nsresult rv = nsDOMEventTargetHelper::AddEventListener(aType,
aListener,
aUseCapture,
aWantsUntrusted,
optional_argc);
if (NS_SUCCEEDED(rv)) {
UpdateMustKeepAlive();
}
return rv;
}
//-----------------------------------------------------------------------------
// nsWebSocket::nsIWebSocket methods:
//-----------------------------------------------------------------------------
@@ -3303,6 +3439,10 @@ nsWebSocket::Close()
}
if (mReadyState == nsIWebSocket::CONNECTING) {
// FailConnection() can release the object if there are no strong event
// listeners, so we keep a reference before calling it
nsRefPtr<nsWebSocket> kungfuDeathGrip = this;
mConnection->FailConnection();
// We need to set the readyState here because mConnection would set it

View File

@@ -86,6 +86,21 @@ public:
NS_IMETHOD Initialize(nsISupports* aOwner, JSContext* aContext,
JSObject* aObject, PRUint32 aArgc, jsval* aArgv);
// nsIDOMEventTarget
NS_IMETHOD AddEventListener(const nsAString& aType,
nsIDOMEventListener* aListener,
PRBool aUseCapture);
NS_IMETHOD RemoveEventListener(const nsAString& aType,
nsIDOMEventListener* aListener,
PRBool aUseCapture);
// nsIDOMNSEventTarget
NS_IMETHOD AddEventListener(const nsAString& aType,
nsIDOMEventListener *aListener,
PRBool aUseCapture,
PRBool aWantsUntrusted,
PRUint8 optional_argc);
static void ReleaseGlobals();
protected:
@@ -100,6 +115,14 @@ protected:
// called from mConnection accordingly to the situation
void SetReadyState(PRUint16 aNewReadyState);
// if there are onopen or onmessage event listeners ("strong event listeners")
// then this method keeps the object alive when js doesn't have strong
// references to it.
void UpdateMustKeepAlive();
// Releases, if necessary, the strong event listeners. ATTENTION, when calling
// this method the object can be released (and possibly collected).
void DontKeepAliveAnyMore();
nsRefPtr<nsDOMEventListenerWrapper> mOnOpenListener;
nsRefPtr<nsDOMEventListenerWrapper> mOnErrorListener;
nsRefPtr<nsDOMEventListenerWrapper> mOnMessageListener;
@@ -109,6 +132,10 @@ protected:
nsString mOriginalURL;
PRPackedBool mSecure; // if true it is using SSL and the wss scheme,
// otherwise it is using the ws scheme with no SSL
PRPackedBool mHasStrongEventListeners;
PRPackedBool mCheckThereAreStrongEventListeners;
nsCString mAsciiHost; // hostname
PRUint32 mPort;
nsCString mResource; // [filepath[?query]]

View File

@@ -6,25 +6,56 @@ import sys
# see the list of tests in test_websocket.html
def web_socket_do_extra_handshake(request):
if request.ws_protocol == "test 6":
sys.exit(0)
elif request.ws_protocol == "test 19":
time.sleep(180)
pass
elif request.ws_protocol == "test 8":
if request.ws_protocol == "test 2.1":
time.sleep(5)
pass
elif request.ws_protocol == "test 9":
time.sleep(5)
pass
elif request.ws_protocol == "test 10.1":
elif request.ws_protocol == "test 10":
time.sleep(5)
pass
elif request.ws_protocol == "test 19":
raise ValueError('Aborting (test 19)')
elif request.ws_protocol == "test 20" or request.ws_protocol == "test 17":
time.sleep(10)
pass
elif request.ws_protocol == "test 22":
time.sleep(60)
pass
else:
pass
def web_socket_transfer_data(request):
if request.ws_protocol == "test 9":
if request.ws_protocol == "test 2.1" or request.ws_protocol == "test 2.2":
msgutil.close_connection(request)
elif request.ws_protocol == "test 6":
resp = "wrong message"
if msgutil.receive_message(request) == "1":
resp = "2"
msgutil.send_message(request, resp.decode('utf-8'))
resp = "wrong message"
if msgutil.receive_message(request) == "3":
resp = "4"
msgutil.send_message(request, resp.decode('utf-8'))
resp = "wrong message"
if msgutil.receive_message(request) == "5":
resp = "あいうえお"
msgutil.send_message(request, resp.decode('utf-8'))
elif request.ws_protocol == "test 7":
try:
while not request.client_terminated:
msgutil.receive_message(request)
except msgutil.ConnectionTerminatedException, e:
pass
msgutil.send_message(request, "server data")
msgutil.send_message(request, "server data")
msgutil.send_message(request, "server data")
msgutil.send_message(request, "server data")
msgutil.send_message(request, "server data")
time.sleep(30)
msgutil.close_connection(request, True)
elif request.ws_protocol == "test 10":
msgutil.close_connection(request)
elif request.ws_protocol == "test 11":
resp = "wrong message"
@@ -41,27 +72,19 @@ def web_socket_transfer_data(request):
request.connection.write('\xff\x00')
msgutil.send_message(request, "server data")
elif request.ws_protocol == "test 15":
sys.exit (0)
elif request.ws_protocol == "test 17":
while not request.client_terminated:
msgutil.send_message(request, "server data")
time.sleep(1)
msgutil.close_connection(request, True)
return
elif request.ws_protocol == "test 17" or request.ws_protocol == "test 21":
time.sleep(5)
resp = "wrong message"
if msgutil.receive_message(request) == "client data":
resp = "server data"
msgutil.send_message(request, resp.decode('utf-8'))
time.sleep(5)
msgutil.close_connection(request)
time.sleep(5)
elif request.ws_protocol == "test 20":
msgutil.send_message(request, "server data")
sys.exit(0)
elif request.ws_protocol == "test 18":
resp = "wrong message"
if msgutil.receive_message(request) == "1":
resp = "2"
msgutil.send_message(request, resp.decode('utf-8'))
resp = "wrong message"
if msgutil.receive_message(request) == "3":
resp = "4"
msgutil.send_message(request, resp.decode('utf-8'))
resp = "wrong message"
if msgutil.receive_message(request) == "5":
resp = "あいうえお"
msgutil.send_message(request, resp.decode('utf-8'))
elif request.ws_protocol == "test 10.1" or request.ws_protocol == "test 10.2":
msgutil.close_connection(request)
while not request.client_terminated:
msgutil.receive_message(request)

View File

@@ -18,16 +18,17 @@
/*
* tests:
* 1. client tries to connect to a http scheme location;
* 2. client tries to connect to an http resource;
* 2. assure serialization of the connections;
* 3. client tries to connect to an non-existent ws server;
* 4. client tries to connect using a relative url;
* 5. client uses an invalid protocol value;
* 6. server closes the tcp connection before establishing the ws connection;
* 7. client calls close() and the server sends the close frame in
* 6. counter and encoding check;
* 7. client calls close() and the server keeps sending messages and it doesn't
* send the close frame;
* 8. client calls close() and the server sends the close frame in
* acknowledgement;
* 8. client closes the connection before the ws connection is established;
* 9. client sends a message before the ws connection is established;
* 10. assure serialization of the connections;
* 9. client closes the connection before the ws connection is established;
* 10. client sends a message before the ws connection is established;
* 11. a simple hello echo;
* 12. client sends a message with bad bytes;
* 13. server sends an invalid message;
@@ -35,54 +36,46 @@
* it keeps sending normal ws messages;
* 15. server closes the tcp connection, but it doesn't send the close frame;
* 16. client calls close() and tries to send a message;
* 17. client calls close() and the server keeps sending messages and it doesn't
* send the close frame;
* 18. counter and encoding check;
* 19. server takes too long to establish the ws connection;
* 17. see bug 572975 - all event listeners set
* 18. client tries to connect to an http resource;
* 19. server closes the tcp connection before establishing the ws connection;
* 20. see bug 572975 - only on error and onclose event listeners set
* 21. see bug 572975 - same as test 17, but delete strong event listeners when
* receiving the message event;
* 22. server takes too long to establish the ws connection;
*/
var first_test = 1;
var last_test = 19;
var last_test = 22;
var current_test = 1;
var current_test = first_test;
var timeoutToAbortTest = 60000;
var timeoutToOpenWS = 25000;
var all_ws = [];
function shouldNotOpen(e)
{
var ws = e.target;
ok(false, "onopen shouldn't be called on test " + ws._testNumber + "!");
if (ws._timeoutToSucceed != undefined) {
clearTimeout(ws._timeoutToSucceed);
}
}
function shouldNotReceiveCloseEvent(e)
{
var ws = e.target;
ok(false, "onclose shouldn't be called on test " + ws._testNumber + "!");
if (ws._timeoutToSucceed != undefined) {
clearTimeout(ws._timeoutToSucceed);
}
}
function shouldCloseCleanly(e)
{
var ws = e.target;
//ok(e.wasClean, "the ws connection in test " + ws._testNumber + " should be closed cleanly");
if (ws._timeoutToSucceed != undefined) {
clearTimeout(ws._timeoutToSucceed);
}
ok(e.wasClean, "the ws connection in test " + ws._testNumber + " should be closed cleanly");
}
function shouldCloseNotCleanly(e)
{
var ws = e.target;
//ok(!e.wasClean, "the ws connection in test " + ws._testNumber + " shouldn't be closed cleanly");
if (ws._timeoutToSucceed != undefined) {
clearTimeout(ws._timeoutToSucceed);
}
ok(!e.wasClean, "the ws connection in test " + ws._testNumber + " shouldn't be closed cleanly");
}
function CreateTestWS(ws_location, ws_protocol)
@@ -119,6 +112,20 @@ function CreateTestWS(ws_location, ws_protocol)
return ws;
}
function forcegc()
{
netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
Components.utils.forceGC();
var wu = window.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
.getInterface(Components.interfaces.nsIDOMWindowUtils);
wu.garbageCollect();
setTimeout(function()
{
netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
wu.garbageCollect();
}, 1);
}
function doTest(number)
{
if (doTest.timeoutId !== null) {
@@ -163,12 +170,28 @@ function test1()
doTest(2);
}
// this test expects that the serialization list to connect to the proxy
// is empty
function test2()
{
var ws = CreateTestWS("ws://mochi.test:8888/tests/content/base/test/file_websocket_http_resource.txt");
ws.onopen = shouldNotOpen;
ws.onclose = shouldNotReceiveCloseEvent;
doTest(3);
var ws1 = CreateTestWS("ws://mochi.test:8888/tests/content/base/test/file_websocket", "test 2.1");
current_test--; // CreateTestWS incremented this
var ws2 = CreateTestWS("ws://mochi.test:8888/tests/content/base/test/file_websocket", "test 2.2");
var ws2CanConnect = false;
// the server will delay ws1 for 5 seconds
ws1.onopen = function()
{
ws2CanConnect = true;
}
ws2.onopen = function()
{
ok(ws2CanConnect, "shouldn't connect yet in test 2!");
doTest(3);
}
}
function test3()
@@ -183,10 +206,10 @@ function test4()
{
try {
var ws = CreateTestWS("file_websocket");
ok(false, "test4 failed");
ok(false, "test 4 failed");
}
catch (e) {
ok(true, "test4 failed");
ok(true, "test 4 failed");
}
doTest(5);
}
@@ -214,9 +237,25 @@ function test5()
function test6()
{
var ws = CreateTestWS("ws://mochi.test:8888/tests/content/base/test/file_websocket", "test 6");
ws.onopen = shouldNotOpen;
ws.onclose = shouldNotReceiveCloseEvent;
doTest(7);
var counter = 1;
ws.onopen = function()
{
ws.send(counter);
}
ws.onmessage = function(e)
{
if (counter == 5) {
ok(e.data == "あいうえお");
ws.close();
doTest(7);
} else {
ok(e.data == counter+1, "bad counter");
counter += 2;
ws.send(counter);
}
}
ws.onclose = shouldCloseCleanly;
ws._receivedCloseEvent = false;
}
function test7()
@@ -228,7 +267,7 @@ function test7()
}
ws.onclose = function(e)
{
shouldCloseCleanly(e);
shouldCloseNotCleanly(e);
doTest(8);
};
ws._receivedCloseEvent = false;
@@ -237,20 +276,35 @@ function test7()
function test8()
{
var ws = CreateTestWS("ws://mochi.test:8888/tests/content/base/test/file_websocket", "test 8");
ws.onopen = function()
{
ws.close();
}
ws.onclose = function(e)
{
shouldCloseCleanly(e);
doTest(9);
};
ws._receivedCloseEvent = false;
}
function test9()
{
var ws = CreateTestWS("ws://mochi.test:8888/tests/content/base/test/file_websocket", "test 9");
ws.onopen = shouldNotOpen;
ws.onclose = function(e)
{
shouldCloseNotCleanly(e);
doTest(9);
doTest(10);
};
ws._receivedCloseEvent = false;
ws.close();
}
function test9()
function test10()
{
var ws = CreateTestWS("ws://mochi.test:8888/tests/content/base/test/file_websocket", "test 9");
var ws = CreateTestWS("ws://mochi.test:8888/tests/content/base/test/file_websocket", "test 10");
ws.onclose = shouldCloseCleanly;
ws._receivedCloseEvent = false;
@@ -261,27 +315,8 @@ function test9()
catch (e) {
ok(true, "Couldn't send data before connecting!");
}
doTest(10);
}
function test10()
{
var ws1 = CreateTestWS("ws://mochi.test:8888/tests/content/base/test/file_websocket", "test 10.1");
current_test--; // CreateTestWS incremented this
var ws2 = CreateTestWS("ws://mochi.test:8888/tests/content/base/test/file_websocket", "test 10.2");
var ws2CanConnect = false;
// the server will delay ws1 for 5 seconds
ws1.onopen = function()
ws.onopen = function()
{
ws2CanConnect = true;
}
ws2.onopen = function()
{
ok(ws2CanConnect, "shouldn't connect yet in test 10!");
doTest(11);
}
}
@@ -336,8 +371,8 @@ function test13()
{
ws._timesCalledOnError++;
if (ws._timesCalledOnError == 2) {
ok(true, "test 13 succeeded");
doTest(14);
ok(true, "test13 succeeded");
}
}
ws.onclose = shouldCloseCleanly;
@@ -387,43 +422,63 @@ function test16()
ws._receivedCloseEvent = false;
}
var status_test17 = "not started";
window._test17 = function()
{
var local_ws = new WebSocket("ws://mochi.test:8888/tests/content/base/test/file_websocket", "test 17");
status_test17 = "started";
local_ws.onopen = function(e)
{
status_test17 = "opened";
e.target.send("client data");
forcegc();
};
local_ws.onerror = function()
{
ok(false, "onerror called on test " + e.target._testNumber + "!");
};
local_ws.onmessage = function(e)
{
ok(e.data == "server data", "Bad message in test 17");
status_test17 = "got message";
forcegc();
};
local_ws.onclose = function(e)
{
ok(status_test17 == "got message", "Didn't got message in test 17!");
shouldCloseCleanly(e);
status_test17 = "closed";
forcegc();
doTest(18);
forcegc();
};
local_ws = null;
window._test17 = null;
forcegc();
}
function test17()
{
var ws = CreateTestWS("ws://mochi.test:8888/tests/content/base/test/file_websocket", "test 17");
ws.onopen = function()
{
ws.close();
}
ws.onclose = function(e)
{
shouldCloseNotCleanly(e);
doTest(18);
};
ws._receivedCloseEvent = false;
window._test17();
}
// The tests that expects that their websockets neither open nor close MUST
// be in the end of the tests, i.e. HERE, in order to prevent blocking the other
// tests.
function test18()
{
var ws = CreateTestWS("ws://mochi.test:8888/tests/content/base/test/file_websocket", "test 18");
var counter = 1;
ws.onopen = function()
{
ws.send(counter);
}
ws.onmessage = function(e)
{
if (counter == 5) {
ok(e.data == "あいうえお");
ws.close();
doTest(19);
} else {
ok(e.data == counter+1, "bad counter");
counter += 2;
ws.send(counter);
}
}
ws.onclose = shouldCloseCleanly;
ws._receivedCloseEvent = false;
var ws = CreateTestWS("ws://mochi.test:8888/tests/content/base/test/file_websocket_http_resource.txt");
ws.onopen = shouldNotOpen;
ws.onclose = shouldNotReceiveCloseEvent;
doTest(19);
}
function test19()
@@ -434,6 +489,81 @@ function test19()
doTest(20);
}
window._test20 = function()
{
var local_ws = new WebSocket("ws://mochi.test:8888/tests/content/base/test/file_websocket", "test 20");
local_ws.onerror = function()
{
ok(false, "onerror called on test " + e.target._testNumber + "!");
};
local_ws.onclose = shouldNotReceiveCloseEvent;
local_ws = null;
window._test20 = null;
forcegc();
}
function test20()
{
window._test20();
doTest(21);
}
var timeoutTest21;
window._test21 = function()
{
var local_ws = new WebSocket("ws://mochi.test:8888/tests/content/base/test/file_websocket", "test 21");
local_ws.onopen = function(e)
{
e.target.send("client data");
timeoutTest21 = setTimeout(function()
{
ok(false, "Didn't received message on test 21!");
}, 15000);
forcegc();
e.target.onopen = null;
forcegc();
};
local_ws.onerror = function()
{
ok(false, "onerror called on test " + e.target._testNumber + "!");
};
local_ws.onmessage = function(e)
{
clearTimeout(timeoutTest21);
ok(e.data == "server data", "Bad message in test 21");
forcegc();
e.target.onmessage = null;
forcegc();
};
local_ws.onclose = shouldNotReceiveCloseEvent;
local_ws = null;
window._test21 = null;
forcegc();
}
function test21()
{
window._test21();
doTest(22);
}
function test22()
{
var ws = CreateTestWS("ws://mochi.test:8888/tests/content/base/test/file_websocket", "test 22");
ws.onopen = shouldNotOpen;
ws.onclose = shouldNotReceiveCloseEvent;
doTest(23);
}
function finishWSTest()
{
for (i = 0; i < all_ws.length; ++i) {

View File

@@ -41,6 +41,7 @@ import Queue
import threading
from mod_pywebsocket import util
from time import time,sleep
class MsgUtilException(Exception):
@@ -71,7 +72,7 @@ def _write(request, bytes):
raise
def close_connection(request):
def close_connection(request, abort=False):
"""Close connection.
Args:
@@ -83,10 +84,25 @@ def close_connection(request):
# running through the following steps:
# 1. send a 0xFF byte and a 0x00 byte to the client to indicate the start
# of the closing handshake.
_write(request, '\xff\x00')
got_exception = False
if not abort:
_write(request, '\xff\x00')
# timeout of 20 seconds to get the client's close frame ack
initial_time = time()
end_time = initial_time + 20
while time() < end_time:
try:
receive_message(request)
except ConnectionTerminatedException, e:
got_exception = True
sleep(1)
request.server_terminated = True
# TODO(ukai): 2. wait until the /client terminated/ flag has been set, or
# until a server-defined timeout expires.
if got_exception:
util.prepend_message_to_exception(
'client initiated closing handshake for %s: ' % (
request.ws_resource),
e)
raise ConnectionTerminatedException
# TODO: 3. close the WebSocket connection.
# note: mod_python Connection (mp_conn) doesn't have close method.