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
+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>.
--- /dev/null
+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
+
--- /dev/null
+<!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>
--- /dev/null
+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
+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".
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
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();
+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".
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.
--- /dev/null
+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
+
--- /dev/null
+<!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>
--- /dev/null
+def web_socket_do_extra_handshake(request):
+ pass
+
+
+def web_socket_transfer_data(request):
+ # After this handler exits, pywebsocket initiates the closing handshake.
+ pass
+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".
+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.
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) {
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.
platformClose();
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;
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; }
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();
}
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
#include "PlatformString.h"
#include "ScriptExecutionContext.h"
+#include "WebSocketChannelClient.h"
#include <wtf/Forward.h>
#include <wtf/OwnPtr.h>
#include <wtf/Threading.h>
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();
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;
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;
}
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.
{
if (m_state == OPEN)
return m_channel->bufferedAmount();
+ else if (m_state == CLOSING)
+ return m_channel->bufferedAmount() + m_bufferedAmountAfterClose;
return m_bufferedAmountAfterClose;
}
{
LOG(Network, "WebSocket %p didConnect", this);
if (m_state != CONNECTING) {
- didClose(0);
+ didClose(0, ClosingHandshakeIncomplete);
return;
}
ASSERT(scriptExecutionContext());
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();
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();
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*);
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)
, 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)
{
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)
m_client = 0;
m_context = 0;
if (m_handle)
- m_handle->close();
+ m_handle->disconnect();
}
void WebSocketChannel::suspend()
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)
m_context = 0;
m_handle = 0;
if (client)
- client->didClose(m_unhandledBufferedAmount);
+ client->didClose(m_unhandledBufferedAmount, m_receivedClosingHandshake ? WebSocketChannelClient::ClosingHandshakeComplete : WebSocketChannelClient::ClosingHandshakeIncomplete);
}
deref();
}
if (!m_context) {
return;
}
+ if (len <= 0) {
+ handle->disconnect();
+ return;
+ }
if (!m_client) {
m_shouldDiscardReceivedData = true;
- handle->close();
+ handle->disconnect();
return;
}
if (m_shouldDiscardReceivedData)
m_context->addMessage(OtherMessageSource, NetworkErrorMessageType, ErrorMessageLevel, message, 0, failingURL, 0);
}
m_shouldDiscardReceivedData = true;
- handle->close();
+ handle->disconnect();
}
void WebSocketChannel::didReceiveAuthenticationChallenge(SocketStreamHandle*, const AuthenticationChallenge&)
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)
skipBuffer(headerLength);
m_shouldDiscardReceivedData = true;
if (!m_closed)
- m_handle->close();
+ m_handle->disconnect();
return false;
}
if (m_handshake.mode() != WebSocketHandshake::Connected)
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;
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)
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();
void skipBuffer(size_t len);
bool processBuffer();
void resumeTimerFired(Timer<WebSocketChannel>* timer);
+ void startClosingHandshake();
+ void closingTimerFired(Timer<WebSocketChannel>*);
ScriptExecutionContext* m_context;
WebSocketChannelClient* m_client;
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;
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() { }
if (!m_mainWebSocketChannel)
return;
m_mainWebSocketChannel->close();
- m_mainWebSocketChannel = 0;
}
void WorkerThreadableWebSocketChannel::Peer::fail(const String& reason)
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)
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);
+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.
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)