mirror of
https://github.com/faye/websocket-driver-ruby.git
synced 2025-11-01 13:59:38 +00:00
248 lines
9.3 KiB
Plaintext
248 lines
9.3 KiB
Plaintext
= websocket-protocol {<img src="https://secure.travis-ci.org/faye/websocket-protocol-ruby.png" />}[http://travis-ci.org/faye/websocket-protocol-ruby]
|
|
|
|
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 send 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 handshake 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
|
|
|
|
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 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:
|
|
|
|
require 'websocket/protocol'
|
|
require 'eventmachine'
|
|
|
|
class WS
|
|
attr_reader :env, :url
|
|
|
|
def initialize(env)
|
|
@env = env
|
|
|
|
secure = Rack::Request.new(env).ssl?
|
|
scheme = secure ? 'wss:' : 'ws:'
|
|
@url = scheme + '//' + env['HTTP_HOST'] + env['REQUEST_URI']
|
|
|
|
@handler = WebSocket::Protocol.rack(self)
|
|
|
|
env['rack.hijack'].call
|
|
@io = env['rack.hijack_io']
|
|
|
|
EM.attach(@io, Reader) { |conn| conn.handler = @handler }
|
|
|
|
@handler.start
|
|
end
|
|
|
|
def write(string)
|
|
@io.write(string)
|
|
end
|
|
|
|
module Reader
|
|
attr_writer :handler
|
|
|
|
def receive_data(string)
|
|
@handler.parse(string)
|
|
end
|
|
end
|
|
end
|
|
|
|
To explain what's going on here: the +WS+ class implements the +env+, +url+ and
|
|
<tt>write(string)</tt> 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
|
|
|
|
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:
|
|
|
|
handler = WebSocket::Protocol.client(socket)
|
|
|
|
After this you use the handler API as described below to process incoming data
|
|
and send outgoing data.
|
|
|
|
|
|
=== Handler API
|
|
|
|
Handlers are created using one of the following methods:
|
|
|
|
handler = WebSocket::Protocol.rack(socket, options)
|
|
handler = WebSocket::Protocol.client(socket, options)
|
|
|
|
The +rack+ 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 most 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.
|
|
|
|
==== <tt>handler.version</tt>
|
|
|
|
Returns the WebSocket version in use as a string. Will either be
|
|
<tt>hixie-75</tt>, <tt>hixie-76</tt> or <tt>hybi-$version</tt>.
|
|
|
|
==== <tt>handler.protocol</tt>
|
|
|
|
Returns a string containing the selected subprotocol, if any was agreed upon
|
|
using the <tt>Sec-WebSocket-Protocol</tt> mechanism. This value becomes
|
|
available after +onopen+ has fired.
|
|
|
|
|
|
== License
|
|
|
|
(The MIT License)
|
|
|
|
Copyright (c) 2009-2013 James Coglan
|
|
|
|
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
|
this software and associated documentation files (the 'Software'), to deal in
|
|
the Software without restriction, including without limitation the rights to use,
|
|
copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
|
Software, and to permit persons to whom the Software is furnished to do so,
|
|
subject to the following conditions:
|
|
|
|
The above copyright notice and this permission notice shall be included in all
|
|
copies or substantial portions of the Software.
|
|
|
|
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
|
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
|
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
|
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
|
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
|