2011-05-26 Yuta Kitamura <yutak@chromium.org>
authoryutak@chromium.org <yutak@chromium.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 27 May 2011 03:24:02 +0000 (03:24 +0000)
committeryutak@chromium.org <yutak@chromium.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 27 May 2011 03:24:02 +0000 (03:24 +0000)
        Reviewed by Kent Tamura.

        WebSocket closing handshake
        https://bugs.webkit.org/show_bug.cgi?id=35721

        * http/tests/websocket/tests/client-close-expected.txt: Added.
        * http/tests/websocket/tests/client-close.html: Added. Test client-initiated close.
        * http/tests/websocket/tests/client-close_wsh.py: Added.
        * http/tests/websocket/tests/close-before-open-expected.txt: Add a new console message.
        * http/tests/websocket/tests/close-event-expected.txt:
        * http/tests/websocket/tests/close-event.html: Test if closeEvent.wasClean is true.
        * http/tests/websocket/tests/close-unref-websocket-expected.txt: Add a new console message.
        * http/tests/websocket/tests/frame-length-longer-than-buffer_wsh.py:
        We need to stop pywebsocket from starting the closing handshake. Otherwise, pywebsocket
        waits for a close frame to arrive and this test will time out.
        * http/tests/websocket/tests/server-close-expected.txt: Added.
        * http/tests/websocket/tests/server-close.html: Added. Test server-initiated close.
        * http/tests/websocket/tests/server-close_wsh.py: Added.
        * http/tests/websocket/tests/websocket-event-target-expected.txt: Add a new console message.
2011-05-26  Yuta Kitamura  <yutak@chromium.org>

        Reviewed by Kent Tamura.

        WebSocket closing handshake
        https://bugs.webkit.org/show_bug.cgi?id=35721

        Implement WebSocket closing handshake based on Ian Hickson's
        WebSocket protocol draft 76.

        Tests: http/tests/websocket/tests/client-close.html
               http/tests/websocket/tests/server-close.html

        * platform/network/SocketStreamHandleBase.cpp:
        (WebCore::SocketStreamHandleBase::send):
        Do not send a message if we are in Closing state.
        (WebCore::SocketStreamHandleBase::close):
        Do not disconnect if we have pending data which have not been sent yet.
        In this case, the actual disconnection will happen in sendPendingData().
        (WebCore::SocketStreamHandleBase::disconnect):
        Renamed from close(). Disconnect the connection immediately.
        (WebCore::SocketStreamHandleBase::sendPendingData):
        * platform/network/SocketStreamHandleBase.h:
        * websockets/ThreadableWebSocketChannelClientWrapper.cpp:
        Add didStartClosingHandshake(). Add a function argument (ClosingHandshakeCompletionStatus)
        to didClose().
        (WebCore::ThreadableWebSocketChannelClientWrapper::didStartClosingHandshake):
        (WebCore::ThreadableWebSocketChannelClientWrapper::didClose):
        (WebCore::ThreadableWebSocketChannelClientWrapper::didStartClosingHandshakeCallback):
        (WebCore::ThreadableWebSocketChannelClientWrapper::didCloseCallback):
        * websockets/ThreadableWebSocketChannelClientWrapper.h:
        * websockets/WebSocket.cpp:
        (WebCore::WebSocket::send):
        (WebCore::WebSocket::close):
        Fail if close() is attempted before the connection is established.
        Otherwise, set the state to CLOSING and start the closing handshake.
        (WebCore::WebSocket::bufferedAmount):
        If the state is CLOSING, we need to consider buffered data in m_channel and sent after close().
        (WebCore::WebSocket::didConnect):
        (WebCore::WebSocket::didReceiveMessage):
        We need to invoke message event in CLOSING state as well as OPEN state.
        (WebCore::WebSocket::didReceiveMessageError):
        (WebCore::WebSocket::didStartClosingHandshake):
        (WebCore::WebSocket::didClose):
        * websockets/WebSocket.h:
        * websockets/WebSocketChannel.cpp:
        (WebCore::WebSocketChannel::WebSocketChannel):
        (WebCore::WebSocketChannel::close):
        Start the closing handshake.
        (WebCore::WebSocketChannel::disconnect):
        Disconnect the socket stream, instead of close.
        (WebCore::WebSocketChannel::didClose):
        (WebCore::WebSocketChannel::didReceiveData): Ditto.
        (WebCore::WebSocketChannel::didFail): Ditto.
        (WebCore::WebSocketChannel::processBuffer):
        Ditto.
        Handle 0xFF 0x00 byte sequence, and discard received data once the closing handshake has started.
        (WebCore::WebSocketChannel::startClosingHandshake):
        Send 0xFF 0x00 byte sequence.
        (WebCore::WebSocketChannel::closingTimerFired):
        Disconnect the socket stream if the closing handshake has timed out.
        * websockets/WebSocketChannel.h:
        m_closing is true if "the WebSocket closing handshake has started" (as stated in the protocol
        specification).
        * websockets/WebSocketChannelClient.h:
        (WebCore::WebSocketChannelClient::didStartClosingHandshake): Added.
        (WebCore::WebSocketChannelClient::didClose): Add closingHandshakeCompletion parameter.
        * websockets/WorkerThreadableWebSocketChannel.cpp:
        Add closingHandshakeCompletion parameter to didClose(), and add didStartClosingHandshake().
        (WebCore::WorkerThreadableWebSocketChannel::Peer::close):
        (WebCore::workerContextDidStartClosingHandshake):
        (WebCore::WorkerThreadableWebSocketChannel::Peer::didStartClosingHandshake):
        (WebCore::workerContextDidClose):
        (WebCore::WorkerThreadableWebSocketChannel::Peer::didClose):
        * websockets/WorkerThreadableWebSocketChannel.h:
2011-05-26  Yuta Kitamura  <yutak@chromium.org>

        Reviewed by Kent Tamura.

        WebSocket closing handshake
        https://bugs.webkit.org/show_bug.cgi?id=35721

        * Scripts/webkitpy/thirdparty/__init__.py:
        Pull in pywebsocket 0.6b1. We need to update pywebsocket
        to get the right behavior of closing handshake.

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@87464 268f45cc-cd09-0410-ab3c-d52691b4dbfc

27 files changed:
LayoutTests/ChangeLog
LayoutTests/http/tests/websocket/tests/client-close-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/websocket/tests/client-close.html [new file with mode: 0644]
LayoutTests/http/tests/websocket/tests/client-close_wsh.py [new file with mode: 0644]
LayoutTests/http/tests/websocket/tests/close-before-open-expected.txt
LayoutTests/http/tests/websocket/tests/close-event-expected.txt
LayoutTests/http/tests/websocket/tests/close-event.html
LayoutTests/http/tests/websocket/tests/close-unref-websocket-expected.txt
LayoutTests/http/tests/websocket/tests/frame-length-longer-than-buffer_wsh.py
LayoutTests/http/tests/websocket/tests/server-close-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/websocket/tests/server-close.html [new file with mode: 0644]
LayoutTests/http/tests/websocket/tests/server-close_wsh.py [new file with mode: 0644]
LayoutTests/http/tests/websocket/tests/websocket-event-target-expected.txt
Source/WebCore/ChangeLog
Source/WebCore/platform/network/SocketStreamHandleBase.cpp
Source/WebCore/platform/network/SocketStreamHandleBase.h
Source/WebCore/websockets/ThreadableWebSocketChannelClientWrapper.cpp
Source/WebCore/websockets/ThreadableWebSocketChannelClientWrapper.h
Source/WebCore/websockets/WebSocket.cpp
Source/WebCore/websockets/WebSocket.h
Source/WebCore/websockets/WebSocketChannel.cpp
Source/WebCore/websockets/WebSocketChannel.h
Source/WebCore/websockets/WebSocketChannelClient.h
Source/WebCore/websockets/WorkerThreadableWebSocketChannel.cpp
Source/WebCore/websockets/WorkerThreadableWebSocketChannel.h
Tools/ChangeLog
Tools/Scripts/webkitpy/thirdparty/__init__.py

index c4b5ceba400385411dbab755a66ddb59163acfd4..d2b73ca187ab1d6014fc924db7dddb201228eca8 100644 (file)
@@ -1,3 +1,25 @@
+2011-05-26  Yuta Kitamura  <yutak@chromium.org>
+
+        Reviewed by Kent Tamura.
+
+        WebSocket closing handshake
+        https://bugs.webkit.org/show_bug.cgi?id=35721
+
+        * http/tests/websocket/tests/client-close-expected.txt: Added.
+        * http/tests/websocket/tests/client-close.html: Added. Test client-initiated close.
+        * http/tests/websocket/tests/client-close_wsh.py: Added.
+        * http/tests/websocket/tests/close-before-open-expected.txt: Add a new console message.
+        * http/tests/websocket/tests/close-event-expected.txt:
+        * http/tests/websocket/tests/close-event.html: Test if closeEvent.wasClean is true.
+        * http/tests/websocket/tests/close-unref-websocket-expected.txt: Add a new console message.
+        * http/tests/websocket/tests/frame-length-longer-than-buffer_wsh.py:
+        We need to stop pywebsocket from starting the closing handshake. Otherwise, pywebsocket
+        waits for a close frame to arrive and this test will time out.
+        * http/tests/websocket/tests/server-close-expected.txt: Added.
+        * http/tests/websocket/tests/server-close.html: Added. Test server-initiated close.
+        * http/tests/websocket/tests/server-close_wsh.py: Added.
+        * http/tests/websocket/tests/websocket-event-target-expected.txt: Add a new console message.
+
 2011-05-26  MORITA Hajime  <morrita@google.com>
 
         Unreviewed expectations update for <details>.
diff --git a/LayoutTests/http/tests/websocket/tests/client-close-expected.txt b/LayoutTests/http/tests/websocket/tests/client-close-expected.txt
new file mode 100644 (file)
index 0000000..5bb6eaa
--- /dev/null
@@ -0,0 +1,11 @@
+WebSocket: Test client-initiated close.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+Connected
+Closed
+PASS closeEvent.wasClean is true
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
diff --git a/LayoutTests/http/tests/websocket/tests/client-close.html b/LayoutTests/http/tests/websocket/tests/client-close.html
new file mode 100644 (file)
index 0000000..dfea21d
--- /dev/null
@@ -0,0 +1,41 @@
+<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">
+<html>
+<head>
+<link rel="stylesheet" href="../../../js-test-resources/js-test-style.css">
+<script src="../../../js-test-resources/js-test-pre.js"></script>
+</head>
+<body>
+<div id="description"></div>
+<div id="console"></div>
+<script type="text/javascript">
+description("WebSocket: Test client-initiated close.");
+
+window.jsTestIsAsync = true;
+
+var ws = new WebSocket("ws://127.0.0.1:8880/websocket/tests/client-close");
+var closeEvent;
+
+ws.onopen = function()
+{
+    debug("Connected");
+    ws.close();
+};
+
+ws.onmessage = function(messageEvent)
+{
+    debug("Received: " + messageEvent.data);
+};
+
+ws.onclose = function(event)
+{
+    debug("Closed");
+    closeEvent = event;
+    shouldBeTrue("closeEvent.wasClean");
+    finishJSTest();
+};
+
+var successfullyParsed = true;
+</script>
+<script src="../../../js-test-resources/js-test-post.js"></script>
+</body>
+</html>
diff --git a/LayoutTests/http/tests/websocket/tests/client-close_wsh.py b/LayoutTests/http/tests/websocket/tests/client-close_wsh.py
new file mode 100644 (file)
index 0000000..3ba91f6
--- /dev/null
@@ -0,0 +1,20 @@
+from mod_pywebsocket import msgutil
+
+
+def web_socket_do_extra_handshake(request):
+    pass
+
+
+def web_socket_transfer_data(request):
+    # Wait for a close frame sent from the client.
+    close_frame = request.ws_stream.receive_bytes(2)
+
+    # If the following assertion fails, AssertionError will be raised,
+    # which will prevent pywebsocket from sending a close frame.
+    # In this case, the client will fail to finish closing handshake, thus
+    # closeEvent.wasClean will become false.
+    assert close_frame == '\xff\x00'
+
+    # Pretend we have received a close frame from the client.
+    # After this function exits, pywebsocket will send a close frame automatically.
+    request.client_terminated = True
index 7a5d92fa8cc1c3a3b0e5e2071ede640a82abb57b..57a542a4dff6523912ed446232c773c68cc7bd21 100644 (file)
@@ -1,3 +1,4 @@
+CONSOLE MESSAGE: line 0: WebSocket is closed before the connection is established.
 Test if Web Socket fires close event when WebSocket is opened and closed fore open event is received.
 
 On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
index 65be8e309fd265d80c034d8752e991d5088d2dc1..a605cc9d92d85636d125278d9c90f0352997fd99 100644 (file)
@@ -7,6 +7,7 @@ Received: 'Hello from Simple WSH.'
 Closed
 PASS closeEventType is "close"
 PASS 'wasClean' in closeEvent is true
+PASS closeEvent.wasClean is true
 PASS Object.getPrototypeOf(closeEvent) === CloseEvent.prototype is true
 PASS Object.getPrototypeOf(closeEvent) !== Event.prototype is true
 PASS successfullyParsed is true
index 967e5d3091b00cac8a154b70b89aa2ba08891ca9..2ba1f0f619b34f863bf0686ee729319af0463a61 100644 (file)
@@ -33,6 +33,7 @@ ws.onclose = function(event)
     closeEventType = closeEvent.type;
     shouldBe("closeEventType", '"close"')
     shouldBeTrue("'wasClean' in closeEvent");
+    shouldBeTrue("closeEvent.wasClean");
     shouldBeTrue("Object.getPrototypeOf(closeEvent) === CloseEvent.prototype");
     shouldBeTrue("Object.getPrototypeOf(closeEvent) !== Event.prototype");
     finishJSTest();
index fc148b076e56521b857ceb6e454246fbc3ad52b7..39f555562b636fa5c3cb71385d3066542fe7f9ef 100644 (file)
@@ -1,3 +1,4 @@
+CONSOLE MESSAGE: line 0: WebSocket is closed before the connection is established.
 Test if Web Socket is closed while handshaking and unreferenced, it should fire close event at most once.
 
 On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
index 0f91c964a7d10fdf5251b67f0e5bfba993712470..767d97cb4848c4b89d1d7b261202669d5e6df004 100644 (file)
@@ -1,10 +1,10 @@
 def web_socket_do_extra_handshake(request):
-  pass
+    pass
 
 def web_socket_transfer_data(request):
-  msg = "\0hello\xff"
-  msg += "\x80\x81\x01"  # skip 1*128+1 bytes.
-  msg += "\x01\xff"
-  msg += "\0should be skipped\xff"
-  request.connection.write(msg)
-  print msg
+    msg = "\0hello\xff"
+    msg += "\x80\x81\x01" # Skip 1*128+1 bytes.
+    msg += "\x01\xff"
+    msg += "\0should be skipped\xff"
+    request.connection.write(msg)
+    raise Exception("Abort the connection") # Prevents pywebsocket from starting closing handshake.
diff --git a/LayoutTests/http/tests/websocket/tests/server-close-expected.txt b/LayoutTests/http/tests/websocket/tests/server-close-expected.txt
new file mode 100644 (file)
index 0000000..feeda9e
--- /dev/null
@@ -0,0 +1,11 @@
+WebSocket: Test server-initiated close.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+Connected
+Closed
+PASS closeEvent.wasClean is true
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
diff --git a/LayoutTests/http/tests/websocket/tests/server-close.html b/LayoutTests/http/tests/websocket/tests/server-close.html
new file mode 100644 (file)
index 0000000..2af487a
--- /dev/null
@@ -0,0 +1,40 @@
+<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">
+<html>
+<head>
+<link rel="stylesheet" href="../../../js-test-resources/js-test-style.css">
+<script src="../../../js-test-resources/js-test-pre.js"></script>
+</head>
+<body>
+<div id="description"></div>
+<div id="console"></div>
+<script type="text/javascript">
+description("WebSocket: Test server-initiated close.");
+
+window.jsTestIsAsync = true;
+
+var ws = new WebSocket("ws://127.0.0.1:8880/websocket/tests/server-close");
+var closeEvent;
+
+ws.onopen = function()
+{
+    debug("Connected");
+};
+
+ws.onmessage = function(messageEvent)
+{
+    debug("Received: " + messageEvent.data);
+};
+
+ws.onclose = function(event)
+{
+    debug("Closed");
+    closeEvent = event;
+    shouldBeTrue("closeEvent.wasClean");
+    finishJSTest();
+};
+
+var successfullyParsed = true;
+</script>
+<script src="../../../js-test-resources/js-test-post.js"></script>
+</body>
+</html>
diff --git a/LayoutTests/http/tests/websocket/tests/server-close_wsh.py b/LayoutTests/http/tests/websocket/tests/server-close_wsh.py
new file mode 100644 (file)
index 0000000..a0698a3
--- /dev/null
@@ -0,0 +1,7 @@
+def web_socket_do_extra_handshake(request):
+    pass
+
+
+def web_socket_transfer_data(request):
+    # After this handler exits, pywebsocket initiates the closing handshake.
+    pass
index 95b1dddd8564458970fd395bd9633ac2df748e8e..379a44ef0190822382497e075f0dee5a85373865 100644 (file)
@@ -1,3 +1,4 @@
+CONSOLE MESSAGE: line 0: WebSocket is closed before the connection is established.
 Make sure WebSocket object acts as EventTarget.
 
 On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
index c4e7ad4604377675a03b7b13eda5077b6816d2e7..925afbe2d8fc7dadfc62e532c9d888d91ef31ea7 100644 (file)
@@ -1,3 +1,79 @@
+2011-05-26  Yuta Kitamura  <yutak@chromium.org>
+
+        Reviewed by Kent Tamura.
+
+        WebSocket closing handshake
+        https://bugs.webkit.org/show_bug.cgi?id=35721
+
+        Implement WebSocket closing handshake based on Ian Hickson's
+        WebSocket protocol draft 76.
+
+        Tests: http/tests/websocket/tests/client-close.html
+               http/tests/websocket/tests/server-close.html
+
+        * platform/network/SocketStreamHandleBase.cpp:
+        (WebCore::SocketStreamHandleBase::send):
+        Do not send a message if we are in Closing state.
+        (WebCore::SocketStreamHandleBase::close):
+        Do not disconnect if we have pending data which have not been sent yet.
+        In this case, the actual disconnection will happen in sendPendingData().
+        (WebCore::SocketStreamHandleBase::disconnect):
+        Renamed from close(). Disconnect the connection immediately.
+        (WebCore::SocketStreamHandleBase::sendPendingData):
+        * platform/network/SocketStreamHandleBase.h:
+        * websockets/ThreadableWebSocketChannelClientWrapper.cpp:
+        Add didStartClosingHandshake(). Add a function argument (ClosingHandshakeCompletionStatus)
+        to didClose().
+        (WebCore::ThreadableWebSocketChannelClientWrapper::didStartClosingHandshake):
+        (WebCore::ThreadableWebSocketChannelClientWrapper::didClose):
+        (WebCore::ThreadableWebSocketChannelClientWrapper::didStartClosingHandshakeCallback):
+        (WebCore::ThreadableWebSocketChannelClientWrapper::didCloseCallback):
+        * websockets/ThreadableWebSocketChannelClientWrapper.h:
+        * websockets/WebSocket.cpp:
+        (WebCore::WebSocket::send):
+        (WebCore::WebSocket::close):
+        Fail if close() is attempted before the connection is established.
+        Otherwise, set the state to CLOSING and start the closing handshake.
+        (WebCore::WebSocket::bufferedAmount):
+        If the state is CLOSING, we need to consider buffered data in m_channel and sent after close().
+        (WebCore::WebSocket::didConnect):
+        (WebCore::WebSocket::didReceiveMessage):
+        We need to invoke message event in CLOSING state as well as OPEN state.
+        (WebCore::WebSocket::didReceiveMessageError):
+        (WebCore::WebSocket::didStartClosingHandshake):
+        (WebCore::WebSocket::didClose):
+        * websockets/WebSocket.h:
+        * websockets/WebSocketChannel.cpp:
+        (WebCore::WebSocketChannel::WebSocketChannel):
+        (WebCore::WebSocketChannel::close):
+        Start the closing handshake.
+        (WebCore::WebSocketChannel::disconnect):
+        Disconnect the socket stream, instead of close.
+        (WebCore::WebSocketChannel::didClose):
+        (WebCore::WebSocketChannel::didReceiveData): Ditto.
+        (WebCore::WebSocketChannel::didFail): Ditto.
+        (WebCore::WebSocketChannel::processBuffer):
+        Ditto.
+        Handle 0xFF 0x00 byte sequence, and discard received data once the closing handshake has started.
+        (WebCore::WebSocketChannel::startClosingHandshake):
+        Send 0xFF 0x00 byte sequence.
+        (WebCore::WebSocketChannel::closingTimerFired):
+        Disconnect the socket stream if the closing handshake has timed out.
+        * websockets/WebSocketChannel.h:
+        m_closing is true if "the WebSocket closing handshake has started" (as stated in the protocol
+        specification).
+        * websockets/WebSocketChannelClient.h:
+        (WebCore::WebSocketChannelClient::didStartClosingHandshake): Added.
+        (WebCore::WebSocketChannelClient::didClose): Add closingHandshakeCompletion parameter.
+        * websockets/WorkerThreadableWebSocketChannel.cpp:
+        Add closingHandshakeCompletion parameter to didClose(), and add didStartClosingHandshake().
+        (WebCore::WorkerThreadableWebSocketChannel::Peer::close):
+        (WebCore::workerContextDidStartClosingHandshake):
+        (WebCore::WorkerThreadableWebSocketChannel::Peer::didStartClosingHandshake):
+        (WebCore::workerContextDidClose):
+        (WebCore::WorkerThreadableWebSocketChannel::Peer::didClose):
+        * websockets/WorkerThreadableWebSocketChannel.h:
+
 2011-05-26  David Levin  <levin@chromium.org>
 
         Reviewed by Dmitry Titov.
index 8472713da2bc306e284756283c5ce52ce5e9e854..d7a7e56c2c21381c0e96aa6fed91d66e078bf8c9 100644 (file)
@@ -52,7 +52,7 @@ SocketStreamHandleBase::SocketStreamState SocketStreamHandleBase::state() const
 
 bool SocketStreamHandleBase::send(const char* data, int length)
 {
-    if (m_state == Connecting)
+    if (m_state == Connecting || m_state == Closing)
         return false;
     if (!m_buffer.isEmpty()) {
         if (m_buffer.size() + length > bufferSize) {
@@ -77,6 +77,16 @@ bool SocketStreamHandleBase::send(const char* data, int length)
 }
 
 void SocketStreamHandleBase::close()
+{
+    if (m_state == Closed)
+        return;
+    m_state = Closing;
+    if (!m_buffer.isEmpty())
+        return;
+    disconnect();
+}
+
+void SocketStreamHandleBase::disconnect()
 {
     RefPtr<SocketStreamHandle> protect(static_cast<SocketStreamHandle*>(this)); // platformClose calls the client, which may make the handle get deallocated immediately.
 
@@ -92,10 +102,16 @@ void SocketStreamHandleBase::setClient(SocketStreamHandleClient* client)
 
 bool SocketStreamHandleBase::sendPendingData()
 {
-    if (m_state != Open)
-        return false;
-    if (m_buffer.isEmpty())
+    if (m_state != Open && m_state != Closing)
         return false;
+    if (m_buffer.isEmpty()) {
+        if (m_state == Open)
+            return false;
+        if (m_state == Closing) {
+            disconnect();
+            return false;
+        }
+    }
     int bytesWritten = platformSend(m_buffer.data(), m_buffer.size());
     if (bytesWritten <= 0)
         return false;
index 608208830f3a28f9df544df6af329a5b4b4fd196..214bbd2f86400f43e06f622c343d81555f589ba2 100644 (file)
@@ -48,7 +48,8 @@ namespace WebCore {
         SocketStreamState state() const;
 
         bool send(const char* data, int length);
-        void close();
+        void close(); // Disconnect after all data in buffer are sent.
+        void disconnect();
         int bufferedAmount() const { return m_buffer.size(); }
 
         SocketStreamHandleClient* client() const { return m_client; }
index 61307d5cb29aa762f537b80ab246af68f728a026..0db25a7e52b6d92259d3d3e4dac9b038d5055011 100644 (file)
@@ -110,9 +110,16 @@ void ThreadableWebSocketChannelClientWrapper::didReceiveMessage(const String& me
         processPendingTasks();
 }
 
-void ThreadableWebSocketChannelClientWrapper::didClose(unsigned long unhandledBufferedAmount)
+void ThreadableWebSocketChannelClientWrapper::didStartClosingHandshake()
 {
-    m_pendingTasks.append(createCallbackTask(&ThreadableWebSocketChannelClientWrapper::didCloseCallback, AllowCrossThreadAccess(this), unhandledBufferedAmount));
+    m_pendingTasks.append(createCallbackTask(&ThreadableWebSocketChannelClientWrapper::didStartClosingHandshakeCallback, AllowCrossThreadAccess(this)));
+    if (!m_suspended)
+        processPendingTasks();
+}
+
+void ThreadableWebSocketChannelClientWrapper::didClose(unsigned long unhandledBufferedAmount, WebSocketChannelClient::ClosingHandshakeCompletionStatus closingHandshakeCompletion)
+{
+    m_pendingTasks.append(createCallbackTask(&ThreadableWebSocketChannelClientWrapper::didCloseCallback, AllowCrossThreadAccess(this), unhandledBufferedAmount, closingHandshakeCompletion));
     if (!m_suspended)
         processPendingTasks();
 }
@@ -151,11 +158,18 @@ void ThreadableWebSocketChannelClientWrapper::didReceiveMessageCallback(ScriptEx
         wrapper->m_client->didReceiveMessage(message);
 }
 
-void ThreadableWebSocketChannelClientWrapper::didCloseCallback(ScriptExecutionContext* context, RefPtr<ThreadableWebSocketChannelClientWrapper> wrapper, unsigned long unhandledBufferedAmount)
+void ThreadableWebSocketChannelClientWrapper::didStartClosingHandshakeCallback(ScriptExecutionContext* context, RefPtr<ThreadableWebSocketChannelClientWrapper> wrapper)
+{
+    ASSERT_UNUSED(context, !context);
+    if (wrapper->m_client)
+        wrapper->m_client->didStartClosingHandshake();
+}
+
+void ThreadableWebSocketChannelClientWrapper::didCloseCallback(ScriptExecutionContext* context, RefPtr<ThreadableWebSocketChannelClientWrapper> wrapper, unsigned long unhandledBufferedAmount, WebSocketChannelClient::ClosingHandshakeCompletionStatus closingHandshakeCompletion)
 {
     ASSERT_UNUSED(context, !context);
     if (wrapper->m_client)
-        wrapper->m_client->didClose(unhandledBufferedAmount);
+        wrapper->m_client->didClose(unhandledBufferedAmount, closingHandshakeCompletion);
 }
 
 } // namespace WebCore
index 74659e2505bee4aed7edb2a49d957eec9f198768..fa5f9ac580ad02510968e433d4ac9385f725a6b2 100644 (file)
@@ -35,6 +35,7 @@
 
 #include "PlatformString.h"
 #include "ScriptExecutionContext.h"
+#include "WebSocketChannelClient.h"
 #include <wtf/Forward.h>
 #include <wtf/OwnPtr.h>
 #include <wtf/Threading.h>
@@ -62,7 +63,8 @@ public:
 
     void didConnect();
     void didReceiveMessage(const String& message);
-    void didClose(unsigned long unhandledBufferedAmount);
+    void didStartClosingHandshake();
+    void didClose(unsigned long unhandledBufferedAmount, WebSocketChannelClient::ClosingHandshakeCompletionStatus);
 
     void suspend();
     void resume();
@@ -73,7 +75,8 @@ protected:
     void processPendingTasks();
     static void didConnectCallback(ScriptExecutionContext*, RefPtr<ThreadableWebSocketChannelClientWrapper>);
     static void didReceiveMessageCallback(ScriptExecutionContext*, RefPtr<ThreadableWebSocketChannelClientWrapper>, String message);
-    static void didCloseCallback(ScriptExecutionContext*, RefPtr<ThreadableWebSocketChannelClientWrapper>, unsigned long unhandledBufferedAmount);
+    static void didStartClosingHandshakeCallback(ScriptExecutionContext*, RefPtr<ThreadableWebSocketChannelClientWrapper>);
+    static void didCloseCallback(ScriptExecutionContext*, RefPtr<ThreadableWebSocketChannelClientWrapper>, unsigned long unhandledBufferedAmount, WebSocketChannelClient::ClosingHandshakeCompletionStatus);
 
     WebSocketChannelClient* m_client;
     bool m_syncMethodDone;
index 7ec8dd4ec5b4039e59ca92984ed870ff848c2f32..fd115ada726bb16aa80f791167c106339e63b1bd 100644 (file)
@@ -164,7 +164,7 @@ bool WebSocket::send(const String& message, ExceptionCode& ec)
         return false;
     }
     // No exception is raised if the connection was once established but has subsequently been closed.
-    if (m_state == CLOSED) {
+    if (m_state == CLOSING || m_state == CLOSED) {
         m_bufferedAmountAfterClose += message.utf8().length() + 2; // 2 for frameing
         return false;
     }
@@ -176,9 +176,14 @@ bool WebSocket::send(const String& message, ExceptionCode& ec)
 void WebSocket::close()
 {
     LOG(Network, "WebSocket %p close", this);
-    if (m_state == CLOSED)
+    if (m_state == CLOSING || m_state == CLOSED)
         return;
-    m_state = CLOSED;
+    if (m_state == CONNECTING) {
+        m_state = CLOSING;
+        m_channel->fail("WebSocket is closed before the connection is established.");
+        return;
+    }
+    m_state = CLOSING;
     m_bufferedAmountAfterClose = m_channel->bufferedAmount();
     // didClose notification may be already queued, which we will inadvertently process while waiting for bufferedAmount() to return.
     // In this case m_channel will be set to null during didClose() call, thus we need to test validness of m_channel here.
@@ -200,6 +205,8 @@ unsigned long WebSocket::bufferedAmount() const
 {
     if (m_state == OPEN)
         return m_channel->bufferedAmount();
+    else if (m_state == CLOSING)
+        return m_channel->bufferedAmount() + m_bufferedAmountAfterClose;
     return m_bufferedAmountAfterClose;
 }
 
@@ -249,7 +256,7 @@ void WebSocket::didConnect()
 {
     LOG(Network, "WebSocket %p didConnect", this);
     if (m_state != CONNECTING) {
-        didClose(0);
+        didClose(0, ClosingHandshakeIncomplete);
         return;
     }
     ASSERT(scriptExecutionContext());
@@ -260,7 +267,7 @@ void WebSocket::didConnect()
 void WebSocket::didReceiveMessage(const String& msg)
 {
     LOG(Network, "WebSocket %p didReceiveMessage %s", this, msg.utf8().data());
-    if (m_state != OPEN)
+    if (m_state != OPEN && m_state != CLOSING)
         return;
     ASSERT(scriptExecutionContext());
     RefPtr<MessageEvent> evt = MessageEvent::create();
@@ -271,22 +278,29 @@ void WebSocket::didReceiveMessage(const String& msg)
 void WebSocket::didReceiveMessageError()
 {
     LOG(Network, "WebSocket %p didReceiveErrorMessage", this);
-    if (m_state != OPEN)
+    if (m_state != OPEN && m_state != CLOSING)
         return;
     ASSERT(scriptExecutionContext());
     dispatchEvent(Event::create(eventNames().errorEvent, false, false));
 }
 
-void WebSocket::didClose(unsigned long unhandledBufferedAmount)
+void WebSocket::didStartClosingHandshake()
+{
+    LOG(Network, "WebSocket %p didStartClosingHandshake", this);
+    m_state = CLOSING;
+}
+
+void WebSocket::didClose(unsigned long unhandledBufferedAmount, ClosingHandshakeCompletionStatus closingHandshakeCompletion)
 {
     LOG(Network, "WebSocket %p didClose", this);
     if (!m_channel)
         return;
+    bool wasClean = m_state == CLOSING && !unhandledBufferedAmount && closingHandshakeCompletion == ClosingHandshakeComplete;
     m_state = CLOSED;
     m_bufferedAmountAfterClose += unhandledBufferedAmount;
     ASSERT(scriptExecutionContext());
     RefPtr<CloseEvent> event = CloseEvent::create(false);
-    event->initCloseEvent(eventNames().closeEvent, false, false, false);
+    event->initCloseEvent(eventNames().closeEvent, false, false, wasClean);
     dispatchEvent(event);
     if (m_channel) {
         m_channel->disconnect();
index 66e645a253f86045fb2c3daee6c0903e31c80a95..191bc3f8896d709551acd19d26b734405b294b1c 100644 (file)
@@ -95,7 +95,8 @@ namespace WebCore {
         virtual void didConnect();
         virtual void didReceiveMessage(const String& message);
         virtual void didReceiveMessageError();
-        virtual void didClose(unsigned long unhandledBufferedAmount);
+        virtual void didStartClosingHandshake();
+        virtual void didClose(unsigned long unhandledBufferedAmount, ClosingHandshakeCompletionStatus);
 
     private:
         WebSocket(ScriptExecutionContext*);
index e3164f15e43ba083d03cfd977f3f0c9b2c0f09c1..0be56e35a7e056bc96c66f2ba1f1b5a1260e4574 100644 (file)
@@ -56,6 +56,8 @@
 
 namespace WebCore {
 
+const double TCPMaximumSegmentLifetime = 2 * 60.0;
+
 WebSocketChannel::WebSocketChannel(ScriptExecutionContext* context, WebSocketChannelClient* client, const KURL& url, const String& protocol)
     : m_context(context)
     , m_client(client)
@@ -64,6 +66,9 @@ WebSocketChannel::WebSocketChannel(ScriptExecutionContext* context, WebSocketCha
     , m_bufferSize(0)
     , m_resumeTimer(this, &WebSocketChannel::resumeTimerFired)
     , m_suspended(false)
+    , m_closing(false)
+    , m_receivedClosingHandshake(false)
+    , m_closingTimer(this, &WebSocketChannel::closingTimerFired)
     , m_closed(false)
     , m_shouldDiscardReceivedData(false)
     , m_unhandledBufferedAmount(0)
@@ -117,8 +122,11 @@ void WebSocketChannel::close()
 {
     LOG(Network, "WebSocketChannel %p close", this);
     ASSERT(!m_suspended);
-    if (m_handle)
-        m_handle->close();  // will call didClose()
+    if (!m_handle)
+        return;
+    startClosingHandshake();
+    if (m_closing && !m_closingTimer.isActive())
+        m_closingTimer.startOneShot(2 * TCPMaximumSegmentLifetime);
 }
 
 void WebSocketChannel::fail(const String& reason)
@@ -140,7 +148,7 @@ void WebSocketChannel::disconnect()
     m_client = 0;
     m_context = 0;
     if (m_handle)
-        m_handle->close();
+        m_handle->disconnect();
 }
 
 void WebSocketChannel::suspend()
@@ -175,6 +183,8 @@ void WebSocketChannel::didClose(SocketStreamHandle* handle)
         InspectorInstrumentation::didCloseWebSocket(m_context, m_identifier);
     ASSERT_UNUSED(handle, handle == m_handle || !m_handle);
     m_closed = true;
+    if (m_closingTimer.isActive())
+        m_closingTimer.stop();
     if (m_handle) {
         m_unhandledBufferedAmount = m_handle->bufferedAmount();
         if (m_suspended)
@@ -184,7 +194,7 @@ void WebSocketChannel::didClose(SocketStreamHandle* handle)
         m_context = 0;
         m_handle = 0;
         if (client)
-            client->didClose(m_unhandledBufferedAmount);
+            client->didClose(m_unhandledBufferedAmount, m_receivedClosingHandshake ? WebSocketChannelClient::ClosingHandshakeComplete : WebSocketChannelClient::ClosingHandshakeIncomplete);
     }
     deref();
 }
@@ -197,9 +207,13 @@ void WebSocketChannel::didReceiveData(SocketStreamHandle* handle, const char* da
     if (!m_context) {
         return;
     }
+    if (len <= 0) {
+        handle->disconnect();
+        return;
+    }
     if (!m_client) {
         m_shouldDiscardReceivedData = true;
-        handle->close();
+        handle->disconnect();
         return;
     }
     if (m_shouldDiscardReceivedData)
@@ -233,7 +247,7 @@ void WebSocketChannel::didFail(SocketStreamHandle* handle, const SocketStreamErr
         m_context->addMessage(OtherMessageSource, NetworkErrorMessageType, ErrorMessageLevel, message, 0, failingURL, 0);
     }
     m_shouldDiscardReceivedData = true;
-    handle->close();
+    handle->disconnect();
 }
 
 void WebSocketChannel::didReceiveAuthenticationChallenge(SocketStreamHandle*, const AuthenticationChallenge&)
@@ -281,9 +295,18 @@ bool WebSocketChannel::processBuffer()
     ASSERT(!m_suspended);
     ASSERT(m_client);
     ASSERT(m_buffer);
+    LOG(Network, "WebSocketChannel %p processBuffer %lu", this, static_cast<unsigned long>(m_bufferSize));
+
     if (m_shouldDiscardReceivedData)
         return false;
 
+    if (m_receivedClosingHandshake) {
+        skipBuffer(m_bufferSize);
+        return false;
+    }
+
+    RefPtr<WebSocketChannel> protect(this); // The client can close the channel, potentially removing the last reference.
+
     if (m_handshake.mode() == WebSocketHandshake::Incomplete) {
         int headerLength = m_handshake.readServerHandshake(m_buffer, m_bufferSize);
         if (headerLength <= 0)
@@ -311,7 +334,7 @@ bool WebSocketChannel::processBuffer()
         skipBuffer(headerLength);
         m_shouldDiscardReceivedData = true;
         if (!m_closed)
-            m_handle->close();
+            m_handle->disconnect();
         return false;
     }
     if (m_handshake.mode() != WebSocketHandshake::Connected)
@@ -362,12 +385,18 @@ bool WebSocketChannel::processBuffer()
             return false;
         }
         ASSERT(p + length >= p);
-        if (p + length < end) {
+        if (p + length <= end) {
             p += length;
             nextFrame = p;
             ASSERT(nextFrame > m_buffer);
             skipBuffer(nextFrame - m_buffer);
-            m_client->didReceiveMessageError();
+            if (frameByte == 0xff && !length) {
+                m_receivedClosingHandshake = true;
+                startClosingHandshake();
+                if (m_closing)
+                    m_handle->close(); // close after sending FF 00.
+            } else
+                m_client->didReceiveMessageError();
             return m_buffer;
         }
         return false;
@@ -405,6 +434,32 @@ void WebSocketChannel::resumeTimerFired(Timer<WebSocketChannel>* timer)
         didClose(m_handle.get());
 }
 
+void WebSocketChannel::startClosingHandshake()
+{
+    LOG(Network, "WebSocketChannel %p closing %d %d", this, m_closing, m_receivedClosingHandshake);
+    if (m_closing)
+        return;
+    ASSERT(m_handle);
+    Vector<char> buf;
+    buf.append('\xff');
+    buf.append('\0');
+    if (!m_handle->send(buf.data(), buf.size())) {
+        m_handle->disconnect();
+        return;
+    }
+    m_closing = true;
+    if (m_client)
+        m_client->didStartClosingHandshake();
+}
+
+void WebSocketChannel::closingTimerFired(Timer<WebSocketChannel>* timer)
+{
+    LOG(Network, "WebSocketChannel %p closing timer", this);
+    ASSERT_UNUSED(timer, &m_closingTimer == timer);
+    if (m_handle)
+        m_handle->disconnect();
+}
+
 }  // namespace WebCore
 
 #endif  // ENABLE(WEB_SOCKETS)
index cd4bdb8c5b5ac67670b93abd1d560daeef95d253..5d572896ccd3212ab63465c155b92be0b26f9c2c 100644 (file)
@@ -57,7 +57,7 @@ namespace WebCore {
         virtual void connect();
         virtual bool send(const String& message);
         virtual unsigned long bufferedAmount() const;
-        virtual void close();
+        virtual void close(); // Start closing handshake.
         virtual void fail(const String& reason);
         virtual void disconnect();
 
@@ -85,6 +85,8 @@ namespace WebCore {
         void skipBuffer(size_t len);
         bool processBuffer();
         void resumeTimerFired(Timer<WebSocketChannel>* timer);
+        void startClosingHandshake();
+        void closingTimerFired(Timer<WebSocketChannel>*);
 
         ScriptExecutionContext* m_context;
         WebSocketChannelClient* m_client;
@@ -95,6 +97,9 @@ namespace WebCore {
 
         Timer<WebSocketChannel> m_resumeTimer;
         bool m_suspended;
+        bool m_closing;
+        bool m_receivedClosingHandshake;
+        Timer<WebSocketChannel> m_closingTimer;
         bool m_closed;
         bool m_shouldDiscardReceivedData;
         unsigned long m_unhandledBufferedAmount;
index 1551073f6d5edd7163bfc24dfa0d3f1046dcb99b..38bd17d00d65f2f2b01f4b9380c0b4e2c7c76875 100644 (file)
@@ -41,7 +41,12 @@ namespace WebCore {
         virtual void didConnect() { }
         virtual void didReceiveMessage(const String&) { }
         virtual void didReceiveMessageError() { }
-        virtual void didClose(unsigned long /* unhandledBufferedAmount */) { }
+        virtual void didStartClosingHandshake() { }
+        enum ClosingHandshakeCompletionStatus {
+            ClosingHandshakeIncomplete,
+            ClosingHandshakeComplete
+        };
+        virtual void didClose(unsigned long /* unhandledBufferedAmount */, ClosingHandshakeCompletionStatus) { }
 
     protected:
         WebSocketChannelClient() { }
index 69fa28872e05390518bab3394a777b1a1abdd3df..00268513598a6a5788e2e1faddf35451b04f5f70 100644 (file)
@@ -174,7 +174,6 @@ void WorkerThreadableWebSocketChannel::Peer::close()
     if (!m_mainWebSocketChannel)
         return;
     m_mainWebSocketChannel->close();
-    m_mainWebSocketChannel = 0;
 }
 
 void WorkerThreadableWebSocketChannel::Peer::fail(const String& reason)
@@ -234,17 +233,29 @@ void WorkerThreadableWebSocketChannel::Peer::didReceiveMessage(const String& mes
     m_loaderProxy.postTaskForModeToWorkerContext(createCallbackTask(&workerContextDidReceiveMessage, m_workerClientWrapper, message), m_taskMode);
 }
 
-static void workerContextDidClose(ScriptExecutionContext* context, RefPtr<ThreadableWebSocketChannelClientWrapper> workerClientWrapper, unsigned long unhandledBufferedAmount)
+static void workerContextDidStartClosingHandshake(ScriptExecutionContext* context, RefPtr<ThreadableWebSocketChannelClientWrapper> workerClientWrapper)
+{
+    ASSERT_UNUSED(context, context->isWorkerContext());
+    workerClientWrapper->didStartClosingHandshake();
+}
+
+void WorkerThreadableWebSocketChannel::Peer::didStartClosingHandshake()
+{
+    ASSERT(isMainThread());
+    m_loaderProxy.postTaskForModeToWorkerContext(createCallbackTask(&workerContextDidStartClosingHandshake, m_workerClientWrapper), m_taskMode);
+}
+
+static void workerContextDidClose(ScriptExecutionContext* context, RefPtr<ThreadableWebSocketChannelClientWrapper> workerClientWrapper, unsigned long unhandledBufferedAmount, WebSocketChannelClient::ClosingHandshakeCompletionStatus closingHandshakeCompletion)
 {
     ASSERT_UNUSED(context, context->isWorkerContext());
-    workerClientWrapper->didClose(unhandledBufferedAmount);
+    workerClientWrapper->didClose(unhandledBufferedAmount, closingHandshakeCompletion);
 }
 
-void WorkerThreadableWebSocketChannel::Peer::didClose(unsigned long unhandledBufferedAmount)
+void WorkerThreadableWebSocketChannel::Peer::didClose(unsigned long unhandledBufferedAmount, ClosingHandshakeCompletionStatus closingHandshakeCompletion)
 {
     ASSERT(isMainThread());
     m_mainWebSocketChannel = 0;
-    m_loaderProxy.postTaskForModeToWorkerContext(createCallbackTask(&workerContextDidClose, m_workerClientWrapper, unhandledBufferedAmount), m_taskMode);
+    m_loaderProxy.postTaskForModeToWorkerContext(createCallbackTask(&workerContextDidClose, m_workerClientWrapper, unhandledBufferedAmount, closingHandshakeCompletion), m_taskMode);
 }
 
 void WorkerThreadableWebSocketChannel::Bridge::setWebSocketChannel(ScriptExecutionContext* context, Bridge* thisPtr, Peer* peer, RefPtr<ThreadableWebSocketChannelClientWrapper> workerClientWrapper)
index 6e09786aab419e8e7e3ec4763d4a875a1f148138..8fdd4b955866afca34986e6ae9de7fd1228a753d 100644 (file)
@@ -99,7 +99,8 @@ private:
 
         virtual void didConnect();
         virtual void didReceiveMessage(const String& message);
-        virtual void didClose(unsigned long unhandledBufferedAmount);
+        virtual void didStartClosingHandshake();
+        virtual void didClose(unsigned long unhandledBufferedAmount, ClosingHandshakeCompletionStatus);
 
     private:
         Peer(RefPtr<ThreadableWebSocketChannelClientWrapper>, WorkerLoaderProxy&, ScriptExecutionContext*, const String& taskMode, const KURL&, const String& protocol);
index f01aebc00da3a80d44a9f6d5508d5093d7301e4f..aa063aa3a1033c1073165503b878509ebf04dcef 100644 (file)
@@ -1,3 +1,14 @@
+2011-05-26  Yuta Kitamura  <yutak@chromium.org>
+
+        Reviewed by Kent Tamura.
+
+        WebSocket closing handshake
+        https://bugs.webkit.org/show_bug.cgi?id=35721
+
+        * Scripts/webkitpy/thirdparty/__init__.py:
+        Pull in pywebsocket 0.6b1. We need to update pywebsocket
+        to get the right behavior of closing handshake.
+
 2011-05-26  Qi Zhang  <qi.2.zhang@nokia.com>
 
         Reviewed by Andreas Kling.
index 7363f5f1245ea94ba8876292a060931fd58f36bb..325adefebf4c6f58581e3e0e83a162343ede1a38 100644 (file)
@@ -123,8 +123,8 @@ class AutoinstallImportHook(object):
     def _install_pywebsocket(self):
         pywebsocket_dir = self._fs.join(_AUTOINSTALLED_DIR, "pywebsocket")
         installer = AutoInstaller(target_dir=pywebsocket_dir)
-        installer.install(url="http://pywebsocket.googlecode.com/files/mod_pywebsocket-0.5.2.tar.gz",
-                          url_subpath="pywebsocket-0.5.2/src/mod_pywebsocket")
+        installer.install(url="http://pywebsocket.googlecode.com/files/mod_pywebsocket-0.6b1.tar.gz",
+                          url_subpath="pywebsocket-0.6b1/src/mod_pywebsocket")
 
     def _install(self, url, url_subpath):
         installer = AutoInstaller(target_dir=_AUTOINSTALLED_DIR)