mirror of
https://github.com/faye/websocket-driver-ruby.git
synced 2025-11-01 13:59:38 +00:00
Documentation that's up to date.
This commit is contained in:
+182
-95
@@ -1,127 +1,214 @@
|
||||
= faye-websocket-parser {<img src="https://secure.travis-ci.org/faye/faye-websocket-parser-ruby.png" />}[http://travis-ci.org/faye/faye-websocket-ruby]
|
||||
= websocket-protocol {<img src="https://secure.travis-ci.org/faye/websocket-protocol-ruby.png" />}[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 <tt>Sec-WebSocket-Protocol</tt>
|
||||
* 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 <tt>Faye::WebSocket::HybiParser</tt> 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:
|
||||
|
||||
* <tt>socket.url</tt> - returns the full URL of the socket as a string.
|
||||
* <tt>socket.write(string)</tt> - writes the given string to a TCP stream.
|
||||
|
||||
Server-side sockets require one additional method:
|
||||
|
||||
* <tt>socket.env</tt> - 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+
|
||||
* <tt>rack.input</tt>, 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 <tt>'https'</tt> if the
|
||||
socket is using an encrypted connection
|
||||
* <tt>rack.input</tt>, 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']
|
||||
|
||||
* <tt>:masking</tt> - optional, boolean flag saying whether to mask outgoing
|
||||
message frames. This is not required for server-side handlers.
|
||||
* <tt>:protocols</tt> - optional, an array of strings that will be used to set
|
||||
the <tt>Sec-WebSocket-Protocol</tt> 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 <tt>parser.handshake_response</tt> 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 <tt>WebSocket::Protocol</tt> 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 <tt>WS#write</tt> 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:
|
||||
|
||||
* <tt>:masking</tt> - must have the value +true+.
|
||||
* <tt>:protocols</tt> - optional, an array of strings that will be used to set
|
||||
the <tt>Sec-WebSocket-Protocol</tt> 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)
|
||||
|
||||
<tt>handshake.request_data</tt> 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:
|
||||
|
||||
* <tt>handshake.complete?</tt> returns +true+ if a whole handshake response has
|
||||
been received from the server
|
||||
* <tt>handshake.valid?</tt> returns +true+ if the server's response is valid
|
||||
and allows the client to connect
|
||||
* <tt>handshake.protocol</tt> is either +nil+ or a string containing the first
|
||||
selected subprotocol that matches both our <tt>:protocols</tt> setting and
|
||||
the server's <tt>Sec-WebSocket-Protocol</tt> 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:
|
||||
|
||||
* <tt>:protocols</tt> - an array of strings representing acceptable
|
||||
subprotocols for use over the socket. The handler will negotiate one of these
|
||||
to use via the <tt>Sec-WebSocket-Protocol</tt> 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
|
||||
<tt>socket.write(string)</tt>.
|
||||
|
||||
==== <tt>handler.onopen { |event| }</tt>
|
||||
|
||||
Sets the handler block to execute when the socket becomes open.
|
||||
|
||||
==== <tt>handler.onmessage { |event| }</tt>
|
||||
|
||||
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.
|
||||
|
||||
==== <tt>handler.onclose { |event| }</tt>
|
||||
|
||||
Sets the handler block to execute when the socket becomes closed. The +event+
|
||||
object has +code+ and +reason+ attributes.
|
||||
|
||||
==== <tt>handler.start</tt>
|
||||
|
||||
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.
|
||||
|
||||
==== <tt>handler.parse(string)</tt>
|
||||
|
||||
Takes a string and parses it, potentially resulting in message events being
|
||||
emitted (see +onmessage+ above) or in data being sent to <tt>socket.write</tt>.
|
||||
You should send all data you receive via I/O to this method.
|
||||
|
||||
==== <tt>handler.text(string)</tt>
|
||||
|
||||
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.
|
||||
|
||||
==== <tt>handler.binary(array)</tt>
|
||||
|
||||
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.
|
||||
|
||||
==== <tt>handler.ping(string = '', &callback)</tt>
|
||||
|
||||
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.
|
||||
|
||||
==== <tt>handler.close</tt>
|
||||
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user