WebKit Web Socket design doc

Authors: ukai, yuzo, tyoshino
Status: Draft (as of Sept 2, 2009)

Table of Contents
  1. WebKit Web Socket design doc
    1. Objective
    2. Background
    3. High Level Structure
    4. WebCore classes
      1. WebCore/websockets/
        1. WebSocket.h
      2. WebCore/platform/network
    5. Flows
      1. Establish WebSocket connection
        1. WebSocket(url [, protocol]) constructor
        2. WebSocketChannel::create()
        3. WebSocketChannel::connect()
        4. SocketStreamHandle::connect()
        5. WebSocketChannel::didOpen()
        6. SocketStreamHandle::send()
        7. WebSocketChannel::didReceiveData()
        8. WebSocket::didConnect()
      2. Send a message (from JavaScript/Browser to server)
        1. WebSocket::send()
        2. WebSocketChannel::send()
        3. WebSocket::close()
        4. WebSocketChannel::close()
        5. SocketStreamHandle::close()
        6. WebSocketChannel::didClose()
        7. WebSocket::didClose()
      3. Receive a message
        1. data received in SocketStreamHandle
        2. WebSocketChannel::didReceiveData()
        3. WebSocket::didReceiveMessage()
      4. WebSocket closed
        1. connection closed in SocketStreamHandle
        2. WebSocketChannel::didClose()
    6. JavaScript binding
      1. WebSocket constructor
      2. WebSocket readonly attribute DOMString URL
      3. WebSocket readonly attribute short readyState
      4. WebSocket readonly attribute unsigned long bufferedAmount
      5. WebSocket attribute Function onopen / onmessage / onclose
      6. WebSocket void send() method
      7. WebSocket void close() method
      8. Garbage Collection
    7. Platform code requirements
      1. SocketStreamHandle::connect()
      2. SocketStreamHandle::send()
      3. SocketStreamHandle::close()
      4. SocketStreamHandle::~SocketStrea,Handle()
    8. Layout Test Plan
    9. Security Considerations
    10. Open Issues:
    11. Reference
    12. Document History

Objective

Web Socket (http://dev.w3.org/html5/websockets/) defines a new API for bi-directional communication between web browser and server.
This document describes Web Socket implementation in WebKit.

Background

Web applications usually access resources on the web using HTTP protocol.  Traditionally, web applications only retrieve static data from web server, but evolved to send form to web server and get dynamic data from web server.  Recent web applications run communicating with web server realtime in background (AJAX).  However, these applications trick http protocol to communicate with web server bidirectionally.

Since these bidirectional communications have become common requirements for rich web applications, several APIs and protocols are proposed.  In HTML5 Web App working group, Web Socket API and protocol are just proposed.  The specification is under development.  Web Socket provides a bi-directional communication mechanism for web browsers  and servers, which should allow for more efficiency where we should currently use hanging requests.  There are certain web apps where this more TCP-like model is much more straightforward than what they have to do today, e.g. Web chat.    Web Socket will help web application developers to build web applications that require bi-directional communications between client (web browser) and server.

High Level Structure



WebCore classes

We'll add the following classes for Web Socket in WebCore/websockets/.

and in WebCore/platform/network

We'll explain overview of these classes interfaces.

WebCore/websockets/

WebSocket.h 

class WebSocket : RefCounted<WebSocket>, public EventTarget, public WebSocketChannelClient, public ActiveDOMObject {  
 public:
   enum State {
     CONNECTING = 0,
     OPEN = 1,
     CLOSED = 2
   };
   static PassRefPtr<WebSockets> create(ScriptExecutionContext* context);

   virtual WebSocket* toWebSocket() { return this; }  // EventTarget.

   void connect(const KURL& url, ExceptionCode&);
   void connect(const KURL& url, const String& protocol, ExceptionCode&);

   void sed(const String& message, ExceptionCode&);

   void close();

   State readyState() const;
   unsigned long bufferedAmount() const;

   void setonopen(PassRefPtr<EventListener> eventListener);
   EventListener* onopen() const;
   void setonmessage(PassRefPtr<EventListener> eventListener);
   EventListener* onmessage() const;
   void setonclose(PassRefPtr<EventListener> eventListener);
   EventListener* onclose() const;

   const KURL& url() const;

 private:
   WebSocket(ScriptExecutionContext* context);

   RefPtr<WebSocketChannel> m_channel;
};

WebSocket implements EventTarget interface and ActiveDOMObject interface.
We'll add new virtual method toWebSocket() in EventTarget interface. Other object that implements EventTarget would return NULL for toWebSocket() method.  
It implements WebSocketChannelClient interface to receive events from WebSocketChannel.

class WebSocketChannel : public RefCounted<WebSocketChannel>, public SocketStreamHandleClient {
 public:
   static PassRefPtr<WebSocketChannel> create(ScriptExecutionContext*,  WebSocketChannelClient*, const KURL& url, const String& protocol);

   void connect();
   void sed(const String& msg);
   void close();

 private:
   WebSocketChannelClient* m_client;
   WebSocketHandshake m_handshake;
   RefPtr<SocketStreamHandle> m_handle; 
};

WebSocketChannel implements SocketStreamHandleClient interface to receive events from SocketStreamHandle.

class WebSocketChannelClient  {
 public:
   virtual void didConnect() {}
   virtual void didReceiveMessage(const String& msg) {}
   virtual void didClose() {}
};

class WebSocketHandshake {
 public:
    WebSocketHandshake(const KURL&, const String& protocol, ScriptExecutionContext*);
    ~WebSocketHandshake();

    CString clientHandshakeMessage() const;
    int readServerHandshake(const char* header, size_t len);

 private:
    const KURL& m_url;
    const String& m_protocol;
    bool m_secure;
    ScriptExecutionContext* m_context;
};

WebCore/platform/network

class SocketStreamHandle  {
  public:
   static PassRefPtr<SocketStreamHandle> create(const KURL&, SocketStreamHandleClient*);

   void send(const char *dataPtr, int dataLength);
   void close();

 private:
    SocketStreamHandleClient* client;
};

class SocketStreamHandleClient {
  public:
    virtual void didOpen(SocketStreamHandle*) {}
    virtual void didReceiveData(SocketStreamHandle*, const char* data, int len) {}
    virtual void didClose() {}
};

Flows

Establish WebSocket connection

WebSocket(url [, protocol]) constructor

  1. create a new WebCore::WebSocket instance by WebCore::WebSocket::create(context).
    1. readyState is CONNECTING.
  2. call websocket->connect(url, protocol, exc) or websocket->connect(url, exc)
    1. parse url.
      1. if url's scheme is not "ws" nor "wss", readyState is CLOSED and throw a SYNTAX_ERR exception.
    1. if protocol is specified, check if it's empty or contains that are not in the range U+0021 .. U+007E.
      1. if so, readyState is CLOSED and throw a SYNTAX_ERR exception.
    2. create a new WebSocketChannel instance, set it in m_channel.  pass the WebSocket instance itself as WebSocketChannelClient to receive events from the WebSocketChannel.
    3. call m_channel->connect()

WebSocketChannel::create()

  1. create a new SocketStreamHandle instance and set it in m_handle.

WebSocketChannel::connect()

  1. request to open a socket stream connection.  call m_handle->connect()

SocketStreamHandle::connect()

  1. Schedule to open a connection for the URL.  Return here and run below in background.
    1. connect TCP to the destination, or via proxy if proxy is configured to use.
      1. if it failed, call close()
    2. perform TLS handshake if it's secure connection.
      1. if it failed, call close()
  2. once connection is open, call client->didOpen()

WebSocketChannel::didOpen()

  1. send m_handshake.clientHandshakeMessage() on m_handle.

SocketStreamHandle::send()

  1. send message.  websocket server shall send handshake message back.
  2. call client->didReceiveData()

WebSocketChannel::didReceiveData()

  1. Perform Web Socket Handshake as described in 3.1 Handshake in "The Web Socket protocol".
    1. receive response message until "\r\n\r\n".  If not, return and wait next didReceiveData().need timeout in handshaking? -Fumitoshi Ukai 6/29/09 4:37 PM 
    2. check handshake message.
      1. if it's ok, call client()->didConnect().
      2. otherwise, call m_handle->close()

WebSocket::didConnect()

  1. change readyState to OPEN.
  2. create simple event called "open" and dispatch the event.  

Send a message (from JavaScript/Browser to server)

WebSocket::send()

  1. if readyState is not OPEN, throw INVALID_STATE_ERR exception. 
  2. verify message.
    1. if it has any unpaired surrogates, throw SYNTAX_ERR exception.
  3. call m_channel->send()

WebSocketChannel::send()

  1. build a websocket frame for the message.  "\0x00<message>\xFF".
  2. call m_handle->send(buf, buflen).

Close a WebSocket (from JavaScript)

WebSocket::close()

  1. if readyState is CLOSED, do nothing.
  2. change readyState to CLOSED.
  3. call m_channel->close()

WebSocketChannel::close()

  1. call m_handle->close()

SocketStreamHandle::close()

  1. close the connection if it's still open.
  2. call client->didClose()

WebSocketChannel::didClose()

  1. destroy m_handle
  2. call m_client->didClose()

WebSocket::didClose()

  1. change readyState to CLOSED (in case, the platform connection is closed).
  2. create simple event called "close" and dispatch the event. 

Receive a message

data received in SocketStreamHandle

  1. call client->didReceiveData()

WebSocketChannel::didReceiveData() 

  1. if it's in handshaking,
    1. accumulate data in buffer and process handshaking
  2. otherwise
    1. accumulate data in buffer, and parse websocket frame
    2. if frame_type=0x00, call m_client->didReceiveMessage()

WebSocket::didReceiveMessage()

  1. check if readyState is OPEN. Otherwise, do nothing and return.
  2. create MessageEvent for the message.
  3. dispatch the MessageEvent.   

WebSocket closed

connection closed in SocketStreamHandle

  1. call client->didClose()

WebSocketChannel::didClose()

  1. destroy m_handle.
  2. call m_client->didClose()

JavaScript binding

JavaScript Engine should bind the JavaScript interface to WebCore::WebSocket as follows:

WebSocket constructor

create new WebCore::WebSocket object and call its connect() method with given parameters.
associate the WebCore::WebSocket object to JavaScript WebSocket object.

WebSocket interface object and constructor is available in WorkerGlobalScope and window.

WebSocket readonly attribute DOMString URL

return url() of associated WebCore::WebSocket object.

WebSocket readonly attribute short readyState

return readyState() of associated WebCore::WebSocket object.

WebSocket readonly attribute unsigned long bufferedAmount

return bufferedAmount() of associated WebCore::WebSocket object.

WebSocket attribute Function onopen / onmessage / onclose

as setter, call setonopen() / setonmessage() / setonclose() methods of associated WebCore::WebSocket object, respectively.
as getter, return onopen() / onmessage() / onclose() methods of associated WebCore::WebSocket object, respectively.

WebSocket void send() method

call send() method of associated WebCore::WebSocket object.

WebSocket void close() method

call close() method of associated WebCore::WebSocket object.

Garbage Collection

WebSocket object that is closed could be garbage collected.
WebSocket object that is open, but no event listeners registered for message events could be garbage collected.

Platform code requirements

SocketStreamHandle::connect()

Schedule opening a connection and return immediately.

It connects the destination over TCP/IP directly, or via proxy if it is configured to use proxy.
If the connection is secure, perform TLS handshaking.
Once raw connection is established, start accepting data on the connection and call client->didOpen() and client will start performing handshaking.

If data are ready to read on the connection, call client->didReceiveData()

If it finds the Web Socket connection is closed, call client->didClose().

SocketStreamHandle::send()

Send data to the socket connection.

SocketStreamHandle::close()

close the underlying socket connection.

SocketStreamHandle::~SocketStrea,Handle()

close the underlying socket connection if it's still open.

Layout Test Plan

We're developing simple Web Socket server implementation in python, so it would be available for webkit layout tests.

Alternative: we'll develop simple Web Socket module for apache, since WebKit uses apache for http layout tests.  But, there are no Web Socket module available. We may need to build the module in WebKit tree.

Planned test cases:
- connecting websocket server
   - simple case
   - fails if no websocket-origin / mismatch websocket-origin
   - via proxy
   - wss (secure)
   - with authentication
      - never transmit Authorization if it's not the same URL with HTTP.
   - cookie?
      - never transmit any HTTP cookies over the ws:// channel if it's marked secure.
   - same-origin / other-origin?
   - many connections
   - confirm it doesn't follow redirection
   - newline in given parameters (URL, resource name, protocol etc)
- connect a websocket to ordinally HTTP server and confirm the that connect fails.
- use ws:// or wss:// in other place than WebSocket constructor and fails to access (do nothing?)
- send/onmessage tests
   - test with echo back server?
   - corrupt data (invalid UTF-8 received)
   - excessively long length encoding: more than int size, or happen to be negative ?
   - performance tests
      - large size of data

Security Considerations

The number of connections:  If it is currently opening a web socket connection to the same host, wait until that connection has been established or failed.  This makes it harder for a script to perform a denial of service attack by just opening a large number of Web Socket connections to a remote host.

Make sure web socket connection is established with unknown/untrusted servers:
- if origin is not exactly equal to websocket-origin in server's response header, fails the connection.
- if websocket url is not exactly equal to websocket-location in server's response header, fails the connection.
- if protocol is specified and it not exactly equal to websocket-protocol in server's response header, fails the connection.

Handle the cookie as defined by the appropriate spec [RFC2109] [RFC2965].

** ISSUE ** ...

Open Issues:

  • should we reuse WebCore/loader instead of adding new component?
    • we choose to keep this out of there and to design the websocket system in a good clean modular fashion.
  • which component is responsible of web socket protocol framing?  This design assumes WebSocketChannel serializes/deserializes message in web socket frame.
    • web socket protocol framing is platform independent, so it would be good to put it in WebSocketChannel rather than WebSocketHandle.
  • what user interaction is desired when some problem occured. such as auth error / TLS/SSL error, ... ?
    • spec says: UA must not convey the failure information to the script, but would be useful for developers to report the problem to the user.

Reference



Document History

Date Author Description
Sept 2, 2009ukaichange to SocketStreamHandle.  update WebSocket methods to follow new draft spec
June 26, 2009ukaiupdating by chromium-dev review feedback
June 23, 2009 ukai Initial draft.