Chromium WebSocket design doc
Contact: tyoshino, yhirano, ricea
Original authors: yuzo, ukai, tyoshino
Status: OBSOLETE (as of November 2014)
Warning
Almost none of this document is relevant any more. It is retained for historical purposes.
Instead, see the design doc for the current WebSocket stack.
Table of Contents
- Chromium WebSocket design doc
- Objective
- High Level Structure
- WebKit API
- Flows
- Establish WebSocket connection
- Send a message
- Receive a message
- Disconnect the connection (from JavaScript)
- Connection closed (from raw connection)
- Other modification
- Security Considerations
- Layout Test Plan
- Open Issues
- Work Estimates
- Reference
- Document History
Objective
We'll implement client side of WebSocket in Chromium.
This document describes Chromium HTML5 WebSocket design.
It is based on WebKit WebSocket design and describes chromium side in more detail.
High Level Structure
Legend:
- green: webkit part (WebCore and platform)
- orange: chromium IPC
- blue: new code in chromium part
- gray: existing code in chromium part
V8WebSocketCustom: custom binding of WebSocket for V8 Engine. It sits between V8 and WebCore::WebSocket.
will be in WebKit/WebCore/binding/v8/custom/.
WebCore::WebSocket: C++ class corresponding to WebSocket interface in JavaScript. It also implements WebCore::WebSocketChannelClient so that it would receive events from WebCore::WebSocketChannelHandle.
will be in WebKit/WebCore/websockets/.
WebCore::WebSocketChannel: WebSocket channel in WebCore. It process WebSocket handshaking and data framing. Use WebCore::WebSocketHandshake to handshake. packing data in a frame and sent it to WebCore::SocketStreamHandle. It implements WebCore::SocketStreamHandleClient, so that it will receive a frame from WebCore::SocketStreamHandle and depacking data from a frame.
WebCore::WebSocketHandshake: Perform WebSocket handshaking.
WebCore::SocketStreamHandle: Socket stream handle in WebCore/platform/network. This is boundary between WebCore and platform.
It handles raw connection establishment (direct / via proxy / TLS) and send/receive data on the connection.
In chromium, it is implemented by WebKit::WebSocketStreamHandle. Each instance of WebKit::WebSocketStreamHandle has a unique socket_id that is used in chromium IPC. socket_id is given by browser process in response of ViewHostMsg_SocketStream_Connect.
In WebKit::WebSocketStreamHandle, it uses webkit_glue::WebSocketStreamHandleBridge to communicate with browser process. it will send ViewHostMsg_SocketStream* messages to browser or receive ViewMsg_SocketStream* messages from browser.
will be in webkit/api/src and webkit/glue. Chromium IPC code is in chrome/browser/renderer_host.
All messages are "control" and async (IPC_MESSAGE_CONTROL), except Connect (this is sync messages). Each socket is identified by socket_id, which is given by SocketStreamHost in browser process in response of ViewHostMsg_SocketStream_Connect.
WebKit::WebSocketStreamHandle: Explained above.
in webkit/api/public/WebSocketStreamHandle.h
webkit_glue::WebSocketStreamHandleImpl: Explained above. Implementation of WebKit::WebSocketStreamHandle
in webkit/glue/websocketstreamhandle_impl.*
webkit_glue::WebSocketStreamHandleBridge: Bridge interface explained above.
in webkit/glue/websocketstreamhandle_bridge.h
IPCWebSocketStreamHandleBridge (internal class): Implementaion of webkit_glue::WebSocketStreamHandleBridge
in chrome/renderer/socket_stream_dispatcher.cc
SocketStreamDispatcher: Handling IPC. Peer of SocketStreamDispatcherHost.
in chrome/renderer/socket_stream_dispatcher.cc
ViewHostMsg_SocketStream*: IPC messages sent from renderer to browser.
- ViewHostMsg_SocketStream_Connect: request to open a new socket. called by WebSocketStreamHandle::connect(). return immediately once it gets socket_id and schedule to open the connection asynchronously.
- GRUL url; destination url
- int* socket_id; response. unique id for the socket handle.
- ViewHostMsg_SocketStream_SendData: trasmit data over the socket connection. called by WebSocketStreamHandle::send(). if more bytes than max_amount_send_allowed will be sent, close the connection. Otherwise, accept the request and data will be sent asynchronously. When data are sent, browser will send ViewMsg_SocketStream_DataSent back.
- int socket_id // Identify socket handle.
- std::vector<char> dataopen issue: size limit? need multiplexing? -ukai 6/23/09 2:14 PM
- ViewHostMsg_SocketStream_Close: close the socket connection. called by WebSocketStreamHandle::close(). socket handle will be alive until ViewMsg_SocketStream_Closed received.
- int request_id // Identify socket handle.
ViewMsg_SocketStream*: IPC messages sent from browser to renderer.
- ViewMsg_SocketStream_Connected: A connection is established. will call WebSocketStreamHandle::client()->didOpen(). renderer can send data on the connection. start receiving data on the connection.
- int socket_id // Identify socket handle.
- int max_amount_send_allowed // maximum amount to be sent at the same time. (max buffer size)
- ViewMsg_SocketStream_DataSent: The socket sends data so that renderer can send data by ViewHostMsg_SocketStream_SendData.
- int socket_id // Identify socket handle.
- int amount_sent // the number of bytes sent on the socket.
- ViewMsg_SocketStream_ReceivedData: data is received on the socket.
- int socket_id // Identify socket handle.
- std::vector<char> data
- ViewMsg_SocketStream_Closed: the socket is closed (in the browser). SocketStreamHandle has been released. socket_id is invalidated.
- int socket_id // Identify socket handle.
SocketStreamDispatcherHost: Processes ViewHostMsg_SocketStream* and sends ViewMsg_SocketStream*. Injected in ResourceMessageFilter.
in chrome/browser/renderer_host/socket_stream_dispatcher_host.*
SocketStreamHost: Host of WebCore::WebSocketStreamHandle. It will receive ViewHostMsg_SocketStream*. messages from renderer.
in chrome/browser/renderer_host/socket_stream_host.*
It manages mapping between socket_id and SocketStream.
SocketStream: Manages socket stream. connect/read/write/close over ClientSocket (net/socket).
in net/socket_stream/socket_stream.*
WebKit API
We'll add new API in WebKit API / WebKitClient API.
webkit/api/public/WebKit.h:
namespace WebKit {
...
// Enables HTML5 WebSocket support.
WEBKIT_API void enableWebSockets();
...
}
webkit/api/public/WebKitClient.h:
namespace WebKit {
...
// Network
...
// Returns a new WebKit::WebSocketStreamHandle instance.
virtual WebSocketStreamHandle* createSocketStreamHandle() = 0;
...
}
webkit/api/public/WebSocketStreamHandle.h:
namespace WebKit {
class WebSocketStreamHandleClient;
class WebData;
class WebSocketStreamHandle {
public:
virtual ~WebSocketStreamHandle() {}
virtual void connect(const WebURL&, WebSocketStreamHandleClient*) = 0;
virtual void send(const WebData& data) = 0;
virtual void close() = 0;
};
}
webkit/api/public/WebSocketStreamHandleClient.h:
namespace WebKit {
class WebSocketStreamHandle;
class WebData;
class WebSocketStreamHandleClient {
public:
virtual void didOpen(WebSocketStreamHandle*, int max_amount_send_allowed) = 0;
virtual void canDataSent(WebSocketStraemHandle*, int amount_sent) = 0;
virtual void didReceiveData(WebSocketStreamHandle*, const WebData&) = 0;
virtual void didClose(WebSocketStreamHandle*) = 0;
};
}
Flows
Establish WebSocket connection
In renderer:
- in JavaScript, WebSocket(url [,protocol]) constructor is called
- create WebCore::WebSocket object and associate it with the JavaScript WebSocket object.
- it calls WebCore::WebSocketChannel::connect(), which calls WebCore::SocketStreamHandle::connect().
- WebCore::SocketStreamHandle::connect() calls WebKit::WebSocketStreamHandle::connect()
- in WebKit::WebSocketStreamHandle::connect(), send ViewHostMsg_SocketStream_Connect message to browser and get socket_id.
In browser: (IO thread)
- handle ViewHostMsg_SocketStream_Connect message by SocketStreamDispatcherHost.
- create SocketStreamHost for the request. Assign new socket_id to it. SocketStreamDispatcherHost is its delegate.
- create SocketStreamRequest for it.
- SocketStreamRequest start job.
- return socket_id to the renderer.
- In SocketStreamJob.
- connect via proxy with method="CONNECT" over TCPClientSocket if proxy is configured.
- use SSLClientSocket if the connection is secure.
- Once connected, send ViewMsg_SocketStream_Connected to the renderer.
In renderer
- handle ViewMsg_SocketStream_Connected message. find associated WebSocketStreamHandle for its socket_id.
- make socket idle.
- call WebSocketClient::didOpen. WebSocketChannel start performing WebSocket handshaking.
- send client handshake message
- receive server handshake message
- check handshake message.
- if it's ok, establish the connection, call m_client->didConnect()
- readyState is OPEN and "open" event is dispatched in JavaScript
Send a message
In renderer:
- in JavaScript, WebSocket::send is used to send message.
- WebCore::WebSocket::send() calls m_channel->send().
- WebCore::WebSocketChannel::send() builds WebSocket frame and call m_handle->send(buf, buflen)
- WebCore::SocketStreamHandle::send() creates WebData from buf,buflen and call WebKit::WebSocketStreamHandle::send().
- In WebKit::WebSocketStreamHandle::send()
- if it will exceed max_amount_send_allowed, return false.
- Otherwise, send ViewHostMsg_SocketStream_SendData message to browser.
In browser: (IO thread)
- handle ViewHostMsg_SocketStream_SendData message by SocketStreamDispatcherHost.
- find SocketStreamHost by socket_id.
- call SocketStreamJob::Send for the SocketStreamHost.
- SocketStreamHost tries to write on ClientSocket.
- ClientSocket wrote data, call canSend of SocketStreamRequest::Delegate.
- SocketStreamDispatcherHost send ViewMsg_SocketStream_DataSent to renderer
In renderer:
- handle ViewMsg_SocketStream_CanSend in SocketStreamDispatcher. find associated WebSocketStreamHandle for its socket_id.
- make socket idle.
- call WebSocketStreamHandleClient::DataSent
Receive a message
In browser: (IO thread)
- ClientSocket receives data from the connection (TCPClientSocket or SSLCLientSocket)
- call didReceiveData of SocketStreamRequest::Delegate.
- SocketStreamDispacherHost send ViewMsg_SocketStream_ReceivedData to renderer
In renderer:
- handle ViewMsg_SocketStream_ReceivedData message. find associated WebSocketStreamHandle for its socket_id.
- call client()->didReceiveData()
- WebCore::WebSocketChannel::didReceive() process WebSocket frame in m_handle->bufferedData()
- remove WebSocket frame from m_handle->bufferedData()
- extract message if frame_type == 0x00, call m_client->didReceiveMessage()
- WebCore::WebSocket::didReceiveMessage() create MessageEvent for the message and dispatch the event.
Disconnect the connection (from JavaScript)
In renderer:
- In JavaScript, WebSocket::close is called
- WebCore::WebSocket::close() change readyState to CLOSED and calls m_channel->close()
- WebCore::WebSocketChannel::disconnect() calls m_handle->close()
- WebCore::SocketStreamHandle::close(), calls WebKit::WebSocketStreamHandle::close()
- WebKit::WebSocketStreamHandle::close() sends ViewHostMsg_SocketStream_Close message to browser
In browser: (IO thread)
- handle ViewHostMsg_SocketStream_Close message by SocketStreamDispatcherHost. find associated SocketStreamHost for its socket_id.
- call SocketStreamJob::Close for the SocketStreamHost.
- SocketStreamHost closes the ClientSocket.
- SocketStreamJob will notice the connection is closed.
Connection closed (from raw connection)
In browser: (IO thread)
- SocketStreamJob notices client socket is not connected. delete the client socket and notify it delegate.
- SocketStreamDispatcherHost sends ViewMsg_SocketStream_Closed to renderer and delete SocketStreamHost for the socket_id.
In renderer:
- handle ViewMsg_SocketStream_Closed message. find associate WebSocketStreamHandle for its socket_id.
- WebSocketStreamHandle calls client()->didClose()
- WebCore::WebSocketChannel::didClose() destroys m_handle and call m_client->didClose()
- WebCore::WebSocket::didClose() changes readyState to CLOSED, create "close" event and dispatch the event.
Other modification
Adds "ws" and "wss" in googleurl. (WebSocket URL scheme)
Security Considerations
See Security Consideration secion in WebKit WebSocket design doc.
** Any chromium specific ISSUE? ** ...
Layout Test Plan
WebKit and Chromium uses different http server for layout tests. WebKit uses apache (launched by WebKitTools/Scripts/run-webkit-httpd, configuration in LayoutTests/http/conf/). Chromium uses lighttpd (launched by webkit/tools/layout_tests/run_webkit_tests.py that imports webkit/tools/layout_tests/layout_package/http_server.py).
Since there are no apache WebSocket module nor lighttpd WebSockets module yet.
So, we're developing simple WebSocket server implementation by python to use it for layout tests in both WebKit and chromium.
Open Issues
- Do we need to split/multiplexing frame in chromium IPC? (in case if websocket transmits huge data and it occupies chromium IPC long time?)
- spec says: "If the user agent is faced with content that is too large to be handled appropriately, then it must fail the WebSocket connection.". how large is the limit?
Work Estimates
By End of '09 Q2: design doc reviewed.
By End of '09 Q3: land minimum functionalities in webkit.
By End of Oct '09: land basic functionalities in chromium/webkit. ready to use in trunk.
Reference
Document History
Date | Author | Description |
Oct 23, 2009 | tyoshino | Updated High Level Architecture section and the diagram in it |
Sept 8, 2009 | ukai | CanSend -> DataSent. add max_amount_send_allowed and amount_sent |
Sept 4, 2009 | ukai | make SendData async (remove accepted) |
Sept 2, 2009 | ukai | Change to SocketStreamHandle. |
June 26, 2009 | ukai | updating by chromium-dev review feedback |
June 23, 2009 | ukai | Initial draft |