diff --git a/README.rdoc b/README.rdoc
index 780fbbd..bd31117 100644
--- a/README.rdoc
+++ b/README.rdoc
@@ -1,127 +1,214 @@
-= faye-websocket-parser {
}[http://travis-ci.org/faye/faye-websocket-ruby]
+= websocket-protocol {
}[http://travis-ci.org/faye/websocket-protocol-ruby]
-This library is a set of WebSocket parsers extracted from the
-{Faye}[http://faye.jcoglan.com] project. It provides parsing and serialization
-functionality for the {draft-75}[http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-75],
-{draft-76}[http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-76],
-and {hybi-07}[http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-07]
-and later versions of the protocol, and can select the right parser to use
-based on the client handshake. It can be used with any Ruby TCP library to
-create both servers and clients.
+This module provides a complete implementation of the WebSocket protocols that
+can be hooked up to any TCP library. It aims to simplify things by decoupling
+the protocol details from the I/O layer, such that users only need to implement
+code to stream data in and out of it without needing to know anything about how
+the protocol actually works. Think of it as a complete WebSocket system with
+pluggable I/O.
+
+Due to this design, you get a lot of things for free. In particular, if you
+hook this module up to some I/O object, it will do all of this for you:
+
+* Select the correct server-side protocol handler to talk to the client
+* Generate and sent both server- and client-side handshakes
+* Recognize when the handshake phase completes and the WS protocol begins
+* Negotiate subprotocol selection based on Sec-WebSocket-Protocol
+* Buffer sent messages until the handshake process is finished
+* Deal with proxies that defer delivery of the draft-76 handshake body
+* Notify you when the socket is open and closed and when messages arrive
+* Recombine fragmented messages
+* Dispatch text, binary, ping and close frames
+* Manage the socket-closing handhake process
+* Automatically reply to ping frames with a matching pong
+* Apply masking to messages sent by the client
+
+This library was originally extracted from the {Faye}[http://faye.jcoglan.com]
+project but now aims to provide simple WebSocket support for any Ruby server or
+I/O system.
== Usage
-For building server-side sockets, you need to provide a Rack-style +env+ hash
-representing the client handshake for Faye to select the right parser for you.
-For client-side sockets, only the Faye::WebSocket::HybiParser may be
-used.
+To build either a server-side or client-side socket, the only requirement is
+that you supply a +socket+ object with these methods:
+
+* socket.url - returns the full URL of the socket as a string.
+* socket.write(string) - writes the given string to a TCP stream.
+
+Server-side sockets require one additional method:
+
+* socket.env - returns a Rack-style env hash that will contain some of
+ the following fields. Their values are strings containing the value of the
+ named header, unless stated otherwise.
+ * +HTTP_CONNECTION+
+ * +HTTP_HOST+
+ * +HTTP_ORIGIN+
+ * +HTTP_SEC_WEBSOCKET_KEY+
+ * +HTTP_SEC_WEBSOCKET_KEY1+
+ * +HTTP_SEC_WEBSOCKET_KEY2+
+ * +HTTP_SEC_WEBSOCKET_PROTOCOL+
+ * +HTTP_SEC_WEBSOCKET_VERSION+
+ * +HTTP_UPGRADE+
+ * rack.input, an +IO+ object representing the request body
+ * +REQUEST_METHOD+, the request's HTTP verb
=== Server-side
-To process the server side of a WebSocket connection, you need to build a Rack
-+env+ hash. This hash will contain a subset of following keys as strings, which
-map to string header values unless states otherwise. The set of keys present
-will depend on which version of the protocol the client is using.
+To handle a server-side WebSocket connection, you need to check whether the
+request is a WebSocket handshake, and if so create a protocol handler for it.
+You must give the handler an object with the +env+, +url+ and +write+ methods.
+A simple example might be:
-* +HTTP_CONNECTION+
-* +HTTP_HOST+
-* +HTTP_ORIGIN+
-* +HTTP_SEC_WEBSOCKET_KEY+
-* +HTTP_SEC_WEBSOCKET_KEY1+
-* +HTTP_SEC_WEBSOCKET_KEY2+
-* +HTTP_SEC_WEBSOCKET_PROTOCOL+
-* +HTTP_SEC_WEBSOCKET_VERSION+
-* +HTTP_UPGRADE+
-* +HTTP_X_FORWARDED_PROTO+, should have the value 'https' if the
- socket is using an encrypted connection
-* rack.input, an +IO+ object representing the request body
-* +REQUEST_METHOD+, the request's HTTP verb
-* +REQUEST_URI+, the request's path and query string
+ require 'websocket/protocol'
+ require 'eventmachine'
-Given an +env+ hash, the first thing to do is check whether it represents a
-valid WebSocket request.
+ class WS
+ attr_reader :env, :url
- is_websocket = Faye::WebSocket.websocket?(env)
+ def initialize(env)
+ @env = env
-If it is a valid WebSocket, you can get a parser for it like so:
+ secure = Rack::Request.new(env).ssl?
+ scheme = secure ? 'ws:' : 'wss:'
+ @url = scheme + '//' + env['HTTP_HOST'] + env['REQUEST_URI']
- parser = Faye::WebSocket.parser(env).new(handler, options)
+ @handler = WebSocket::Protocol.server(self)
-See 'Parser API' below for the +parser+ interface and the API expected on the
-+handler+ object. +options+ is an optional hash containing:
+ env['rack.hijack'].call
+ @io = env['rack.hijack_io']
-* :masking - optional, boolean flag saying whether to mask outgoing
- message frames. This is not required for server-side handlers.
-* :protocols - optional, an array of strings that will be used to set
- the Sec-WebSocket-Protocol header for subprotocol negotiation.
+ EM.attach(@io, Reader) { |conn| conn.handler = @handler }
-To complete the connection, you need to send a response to the client's
-handshake, which you do by writing parser.handshake_response to the
-TCP socket:
+ @handler.start
+ end
- socket.write(parser.handshake_response)
+ def write(string)
+ @io.write(string)
+ end
-At this point the handshake might still not be complete. In some situations
-part of the client's handshake is deferred until the server has sent this
-response, and the server should not begin sending message frames until the
-handshake is done. You should check the connection is ready to exchange
-messages using:
+ module Reader
+ attr_writer :handler
- can_send_messages = parser.open?
+ def receive_data(string)
+ @handler.parse(string)
+ end
+ end
+ end
-After this setup is done, use the parser API detailed below to process data.
+To explain what's going on here: the +WS+ class implements the +env+, +url+ and
++write(string)+ methods as required. When instantiated with a Rack environment,
+it stores the environment and infers the complete URL from it. Having set up
+the +env+ and +url+, it asks WebSocket::Protocol for a server-side
+handler for the socket. Then it uses the Rack hijack API to gain access to the
+TCP stream, and uses EventMachine to stream in incoming data from the client,
+handing incoming data off to the handler for parsing. Finally, we tell the
+handler to +start+, which will begin sending the handshake response. This will
+invoke the WS#write method, which will send the response out over the
+TCP socket.
+
+Having defined this class we could use it like this when handling a request:
+
+ if WebSocket::Protocol.websocket?(env)
+ socket = WS.new(env)
+ end
+
+The handler API is described in full below.
=== Client-side
-If you're building a client, construct a parser like this:
+Similarly, to implement a WebSocket client you just need an object with +url+
+and +write+ methods. Once you have one such object, you ask for a handler for
+it:
- parser = Faye::WebSocket:HybiParser.new(handler, options)
+ handler = WebSocket::Protocol.client(socket)
-The +parser+ interface and the required +handler+ API is documented below under
-'Parser API'. +options+ is a hash containing the following keys:
-
-* :masking - must have the value +true+.
-* :protocols - optional, an array of strings that will be used to set
- the Sec-WebSocket-Protocol header for subprotocol negotiation.
-
-To make a connection, you must send a WebSocket handshake request over TCP to
-the target server.
-
- handshake = parser.create_handshake
- socket.write(handshake.request_data)
-
-handshake.request_data is a string containing the handshake format.
-Send this over an open TCP socket, then parse any data you receive over the
-socket. Here, +chunk+ is a string representing a chunk of input read from the
-socket.
-
- overflow_bytes = handshake.parse(chunk)
-
-If the handshake is complete, any bytes received after the end of the handshake
-are returned as an array of ints. After parsing each chunk of input, use the
-following to check for handshake completion:
-
-* handshake.complete? returns +true+ if a whole handshake response has
- been received from the server
-* handshake.valid? returns +true+ if the server's response is valid
- and allows the client to connect
-* handshake.protocol is either +nil+ or a string containing the first
- selected subprotocol that matches both our :protocols setting and
- the server's Sec-WebSocket-Protocol response
-
-Once you know you have a valid connection, you should have the parser process
-the overflow bytes from the handshake parser:
-
- parser.parse(overflow_bytes)
-
-After this, use the parser API detailed below to handle data tranfer on the
-connection.
+After this you use the handler API as described below to process incoming data
+and send outgoing data.
-=== Parser API
+=== Handler API
+
+Handlers are create using one of the following methods:
+
+ handler = WebSocket::Protocol.server(socket, options)
+ handler = WebSocket::Protocol.client(socket, options)
+
+The +server+ method returns a handler chosen using the socket's +env+. The
++client+ method always returns a handler for the RFC version of the protocol
+with masking enabled on outgoing frames.
+
+The +options+ argument is optional, and is a hash. It may contain the following
+keys:
+
+* :protocols - an array of strings representing acceptable
+ subprotocols for use over the socket. The handler will negotiate one of these
+ to use via the Sec-WebSocket-Protocol header if supported by the
+ other peer.
+
+All handlers respond to the following API methods, but some of them are no-ops
+depending on whether the client supports the behaviour.
+
+Note that all of these methods are commands: if they produce data that should
+be sent over the socket, they will give this to you by calling
+socket.write(string).
+
+==== handler.onopen { |event| }
+
+Sets the handler block to execute when the socket becomes open.
+
+==== handler.onmessage { |event| }
+
+Sets the handler block to execute when a message is received. +event+ will have
+a +data+ attribute containing either a string in the case of a text message or
+an array of integers in the case of a binary message.
+
+==== handler.onclose { |event| }
+
+Sets the handler block to execute when the socket becomes closed. The +event+
+object has +code+ and +reason+ attributes.
+
+==== handler.start
+
+Initiates the protocol by sending the handshake - either the response for a
+server-side handler or the request for a client-side one. This should be the
+first method you invoke. Returns +true+ iff a handshake was sent.
+
+==== handler.parse(string)
+
+Takes a string and parses it, potentially resulting in message events being
+emitted (see +onmessage+ above) or in data being sent to socket.write.
+You should send all data you receive via I/O to this method.
+
+==== handler.text(string)
+
+Sends a text message over the socket. If the socket handshake is not yet
+complete, the message will be queued until it is. Returns +true+ if the message
+was sent or queued, and +false+ if the socket can no longer send messages.
+
+==== handler.binary(array)
+
+Takes an array of byte-sized integers and sends them as a binary message. Will
+queue and return +true+ or +false+ the same way as the +text+ method. It will
+also return +false+ if the handler does not support binary messages.
+
+==== handler.ping(string = '', &callback)
+
+Sends a ping frame over the socket, queueing it if necessary. +string+ and the
++callback+ block are both optional. If a callback is given, it will be invoked
+when the socket receives a pong frame whose content matches +string+. Returns
++false+ if frames can no longer be sent, or if the handler does not support
+ping/pong.
+
+==== handler.close
+
+Initiates the closing handshake if the socket is still open. For handlers with
+no closing handshake, this will result in the immediate execution of the
++onclose+ handler. For handlers with a closing handshake, this sends a closing
+frame and +onclose+ will execute when a response is received or a protocol
+error occurs.
== License