mirror of
https://github.com/faye/websocket-driver-ruby.git
synced 2025-11-01 13:59:38 +00:00
Rename the module to websocket-driver.
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
# websocket-protocol [](https://travis-ci.org/faye/websocket-protocol-ruby)
|
||||
# websocket-driver [](https://travis-ci.org/faye/websocket-driver-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
|
||||
@@ -10,7 +10,7 @@ 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
|
||||
* Select the correct server-side driver 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 `Sec-WebSocket-Protocol`
|
||||
@@ -31,7 +31,7 @@ I/O system.
|
||||
## Installation
|
||||
|
||||
```
|
||||
$ gem install websocket-protocol
|
||||
$ gem install websocket-driver
|
||||
```
|
||||
|
||||
|
||||
@@ -64,12 +64,12 @@ Server-side sockets require one additional method:
|
||||
### 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.
|
||||
request is a WebSocket handshake, and if so create a protocol driver for it.
|
||||
You must give the driver an object with the `env`, `url` and `write` methods.
|
||||
A simple example might be:
|
||||
|
||||
```ruby
|
||||
require 'websocket/protocol'
|
||||
require 'websocket/driver'
|
||||
require 'eventmachine'
|
||||
|
||||
class WS
|
||||
@@ -82,14 +82,14 @@ class WS
|
||||
scheme = secure ? 'wss:' : 'ws:'
|
||||
@url = scheme + '//' + env['HTTP_HOST'] + env['REQUEST_URI']
|
||||
|
||||
@handler = WebSocket::Protocol.rack(self)
|
||||
@driver = WebSocket::Driver.rack(self)
|
||||
|
||||
env['rack.hijack'].call
|
||||
@io = env['rack.hijack_io']
|
||||
|
||||
EM.attach(@io, Reader) { |conn| conn.handler = @handler }
|
||||
EM.attach(@io, Reader) { |conn| conn.driver = @driver }
|
||||
|
||||
@handler.start
|
||||
@driver.start
|
||||
end
|
||||
|
||||
def write(string)
|
||||
@@ -97,10 +97,10 @@ class WS
|
||||
end
|
||||
|
||||
module Reader
|
||||
attr_writer :handler
|
||||
attr_writer :driver
|
||||
|
||||
def receive_data(string)
|
||||
@handler.parse(string)
|
||||
@driver.parse(string)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -109,132 +109,131 @@ end
|
||||
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.
|
||||
the `env` and `url`, it asks `WebSocket::Driver` for a server-side driver 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 driver for parsing. Finally, we tell the driver 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:
|
||||
|
||||
```ruby
|
||||
if WebSocket::Protocol.websocket?(env)
|
||||
if WebSocket::Driver.websocket?(env)
|
||||
socket = WS.new(env)
|
||||
end
|
||||
```
|
||||
|
||||
The handler API is described in full below.
|
||||
The driver API is described in full below.
|
||||
|
||||
|
||||
### Client-side
|
||||
|
||||
Similarly, to implement a WebSocket client you need an object with `url` and
|
||||
`write` methods. Once you have one such object, you ask for a handler for it:
|
||||
`write` methods. Once you have one such object, you ask for a driver for it:
|
||||
|
||||
```ruby
|
||||
handler = WebSocket::Protocol.client(socket)
|
||||
driver = WebSocket::Driver.client(socket)
|
||||
```
|
||||
|
||||
After this you use the handler API as described below to process incoming data
|
||||
After this you use the driver API as described below to process incoming data
|
||||
and send outgoing data.
|
||||
|
||||
|
||||
### Handler API
|
||||
### Driver API
|
||||
|
||||
Handlers are created using one of the following methods:
|
||||
Drivers are created using one of the following methods:
|
||||
|
||||
```ruby
|
||||
handler = WebSocket::Protocol.rack(socket, options)
|
||||
handler = WebSocket::Protocol.client(socket, options)
|
||||
driver = WebSocket::Driver.rack(socket, options)
|
||||
driver = WebSocket::Driver.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
|
||||
The `rack` method returns a driver chosen using the socket's `env`. The
|
||||
`client` method always returns a driver 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
|
||||
use over the socket. The driver 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
|
||||
All drivers 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
|
||||
`socket.write(string)`.
|
||||
|
||||
#### `handler.on('open') { |event| }`
|
||||
#### `driver.on('open') { |event| }`
|
||||
|
||||
Sets the callback block to execute when the socket becomes open.
|
||||
|
||||
#### `handler.on('message') { |event| }`
|
||||
#### `driver.on('message') { |event| }`
|
||||
|
||||
Sets the callback 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.on('error') { |event| }`
|
||||
#### `driver.on('error') { |event| }`
|
||||
|
||||
Sets the callback to execute when a protocol error occurs due to the other peer
|
||||
sending an invalid byte sequence. `event` will have a `message` attribute
|
||||
describing the error.
|
||||
|
||||
#### `handler.on('close') { |event| }`
|
||||
#### `driver.on('close') { |event| }`
|
||||
|
||||
Sets the callback block to execute when the socket becomes closed. The `event`
|
||||
object has `code` and `reason` attributes.
|
||||
|
||||
#### `handler.start`
|
||||
#### `driver.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
|
||||
server-side driver 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)`
|
||||
#### `driver.parse(string)`
|
||||
|
||||
Takes a string and parses it, potentially resulting in message events being
|
||||
emitted (see `on('message')` 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)`
|
||||
#### `driver.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)`
|
||||
#### `driver.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.
|
||||
also return `false` if the driver does not support binary messages.
|
||||
|
||||
#### `handler.ping(string = '', &callback)`
|
||||
#### `driver.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
|
||||
`false` if frames can no longer be sent, or if the driver does not support
|
||||
ping/pong.
|
||||
|
||||
#### `handler.close`
|
||||
#### `driver.close`
|
||||
|
||||
Initiates the closing handshake if the socket is still open. For handlers with
|
||||
Initiates the closing handshake if the socket is still open. For drivers with
|
||||
no closing handshake, this will result in the immediate execution of the
|
||||
`on('close')` handler. For handlers with a closing handshake, this sends a
|
||||
`on('close')` callback. For drivers with a closing handshake, this sends a
|
||||
closing frame and `emit('close')` will execute when a response is received or a
|
||||
protocol error occurs.
|
||||
|
||||
#### `handler.version`
|
||||
#### `driver.version`
|
||||
|
||||
Returns the WebSocket version in use as a string. Will either be `hixie-75`,
|
||||
`hixie-76` or `hybi-$version`.
|
||||
|
||||
#### `handler.protocol`
|
||||
#### `driver.protocol`
|
||||
|
||||
Returns a string containing the selected subprotocol, if any was agreed upon
|
||||
using the `Sec-WebSocket-Protocol` mechanism. This value becomes available
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
require 'rubygems/package_task'
|
||||
|
||||
spec = Gem::Specification.load('websocket-protocol.gemspec')
|
||||
spec = Gem::Specification.load('websocket-driver.gemspec')
|
||||
|
||||
Gem::PackageTask.new(spec) do |pkg|
|
||||
end
|
||||
|
||||
@@ -12,9 +12,9 @@ require 'stringio'
|
||||
require 'uri'
|
||||
|
||||
module WebSocket
|
||||
class Protocol
|
||||
class Driver
|
||||
|
||||
root = File.expand_path('../protocol', __FILE__)
|
||||
root = File.expand_path('../driver', __FILE__)
|
||||
require root + '/../../websocket_mask'
|
||||
|
||||
if RUBY_PLATFORM =~ /java/
|
||||
@@ -1,5 +1,5 @@
|
||||
module WebSocket
|
||||
class Protocol
|
||||
class Driver
|
||||
|
||||
class Client < Hybi
|
||||
def self.generate_key
|
||||
@@ -71,7 +71,7 @@ module WebSocket
|
||||
end
|
||||
|
||||
def validate_handshake
|
||||
data = Protocol.encode(@buffer)
|
||||
data = Driver.encode(@buffer)
|
||||
@buffer = []
|
||||
response = Net::HTTPResponse.read_new(Net::BufferedIO.new(StringIO.new(data)))
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
module WebSocket
|
||||
class Protocol
|
||||
class Driver
|
||||
|
||||
class Draft75 < Protocol
|
||||
class Draft75 < Driver
|
||||
def initialize(socket, options = {})
|
||||
super
|
||||
@stage = 0
|
||||
@@ -41,7 +41,7 @@ module WebSocket
|
||||
|
||||
when 2 then
|
||||
if data == 0xFF
|
||||
emit(:message, MessageEvent.new(Protocol.encode(@buffer)))
|
||||
emit(:message, MessageEvent.new(Driver.encode(@buffer)))
|
||||
@stage = 0
|
||||
else
|
||||
if @length
|
||||
@@ -57,8 +57,8 @@ module WebSocket
|
||||
|
||||
def frame(data, type = nil, error_type = nil)
|
||||
return queue([data, type, error_type]) if @ready_state == 0
|
||||
data = Protocol.encode(data)
|
||||
frame = ["\x00", data, "\xFF"].map(&Protocol.method(:encode)) * ''
|
||||
data = Driver.encode(data)
|
||||
frame = ["\x00", data, "\xFF"].map(&Driver.method(:encode)) * ''
|
||||
@socket.write(frame)
|
||||
true
|
||||
end
|
||||
@@ -1,5 +1,5 @@
|
||||
module WebSocket
|
||||
class Protocol
|
||||
class Driver
|
||||
|
||||
class Draft76 < Draft75
|
||||
BODY_SIZE = 8
|
||||
@@ -1,5 +1,5 @@
|
||||
module WebSocket
|
||||
class Protocol
|
||||
class Driver
|
||||
|
||||
module EventEmitter
|
||||
def initialize
|
||||
@@ -1,7 +1,7 @@
|
||||
module WebSocket
|
||||
class Protocol
|
||||
class Driver
|
||||
|
||||
class Hybi < Protocol
|
||||
class Hybi < Driver
|
||||
root = File.expand_path('../hybi', __FILE__)
|
||||
autoload :StreamReader, root + '/stream_reader'
|
||||
|
||||
@@ -107,7 +107,7 @@ module WebSocket
|
||||
return false unless @ready_state == 1
|
||||
|
||||
data = data.to_s unless Array === data
|
||||
data = Protocol.encode(data) if String === data
|
||||
data = Driver.encode(data) if String === data
|
||||
|
||||
is_text = (String === data)
|
||||
opcode = OPCODES[type || (is_text ? :text : :binary)]
|
||||
@@ -151,7 +151,7 @@ module WebSocket
|
||||
|
||||
frame.concat(buffer)
|
||||
|
||||
@socket.write(Protocol.encode(frame))
|
||||
@socket.write(Driver.encode(frame))
|
||||
true
|
||||
end
|
||||
|
||||
@@ -292,7 +292,7 @@ module WebSocket
|
||||
@buffer.concat(payload)
|
||||
if @final
|
||||
message = @buffer
|
||||
message = Protocol.encode(message, true) if @mode == :text
|
||||
message = Driver.encode(message, true) if @mode == :text
|
||||
reset
|
||||
if message
|
||||
emit(:message, MessageEvent.new(message))
|
||||
@@ -303,7 +303,7 @@ module WebSocket
|
||||
|
||||
when OPCODES[:text] then
|
||||
if @final
|
||||
message = Protocol.encode(payload, true)
|
||||
message = Driver.encode(payload, true)
|
||||
if message
|
||||
emit(:message, MessageEvent.new(message))
|
||||
else
|
||||
@@ -331,18 +331,18 @@ module WebSocket
|
||||
code = ERRORS[:protocol_error]
|
||||
end
|
||||
|
||||
if payload.size > 125 or not Protocol.valid_utf8?(payload[2..-1] || [])
|
||||
if payload.size > 125 or not Driver.valid_utf8?(payload[2..-1] || [])
|
||||
code = ERRORS[:protocol_error]
|
||||
end
|
||||
|
||||
reason = (payload.size > 2) ? Protocol.encode(payload[2..-1], true) : ''
|
||||
reason = (payload.size > 2) ? Driver.encode(payload[2..-1], true) : ''
|
||||
shutdown(code, reason || '')
|
||||
|
||||
when OPCODES[:ping] then
|
||||
frame(payload, :pong)
|
||||
|
||||
when OPCODES[:pong] then
|
||||
message = Protocol.encode(payload, true)
|
||||
message = Driver.encode(payload, true)
|
||||
callback = @ping_callbacks[message]
|
||||
@ping_callbacks.delete(message)
|
||||
callback.call if callback
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
module WebSocket
|
||||
class Protocol
|
||||
class Driver
|
||||
|
||||
class Hybi
|
||||
class StreamReader
|
||||
@@ -1,5 +1,5 @@
|
||||
module WebSocket
|
||||
class Protocol
|
||||
class Driver
|
||||
# http://www.w3.org/International/questions/qa-forms-utf-8.en.php
|
||||
UTF8_MATCH = /^([\x00-\x7F]|[\xC2-\xDF][\x80-\xBF]|\xE0[\xA0-\xBF][\x80-\xBF]|[\xE1-\xEC\xEE\xEF][\x80-\xBF]{2}|\xED[\x80-\x9F][\x80-\xBF]|\xF0[\x90-\xBF][\x80-\xBF]{2}|[\xF1-\xF3][\x80-\xBF]{3}|\xF4[\x80-\x8F][\x80-\xBF]{2})*$/
|
||||
end
|
||||
+2
-2
@@ -1,8 +1,8 @@
|
||||
require 'rubygems'
|
||||
require 'bundler/setup'
|
||||
|
||||
require File.expand_path('../../lib/websocket/protocol', __FILE__)
|
||||
require File.expand_path('../websocket/protocol/draft75_examples', __FILE__)
|
||||
require File.expand_path('../../lib/websocket/driver', __FILE__)
|
||||
require File.expand_path('../websocket/driver/draft75_examples', __FILE__)
|
||||
|
||||
module EncodingHelper
|
||||
def encode(message)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
require "spec_helper"
|
||||
|
||||
describe WebSocket::Protocol::Client do
|
||||
describe WebSocket::Driver::Client do
|
||||
include EncodingHelper
|
||||
|
||||
let :socket do
|
||||
@@ -18,13 +18,13 @@ describe WebSocket::Protocol::Client do
|
||||
nil
|
||||
end
|
||||
|
||||
let :protocol do
|
||||
protocol = WebSocket::Protocol::Client.new(socket, options)
|
||||
protocol.on(:open) { |e| @open = true }
|
||||
protocol.on(:message) { |e| @message += e.data }
|
||||
protocol.on(:error) { |e| @error = e }
|
||||
protocol.on(:close) { |e| @close = [e.code, e.reason] }
|
||||
protocol
|
||||
let :driver do
|
||||
driver = WebSocket::Driver::Client.new(socket, options)
|
||||
driver.on(:open) { |e| @open = true }
|
||||
driver.on(:message) { |e| @message += e.data }
|
||||
driver.on(:error) { |e| @error = e }
|
||||
driver.on(:close) { |e| @close = [e.code, e.reason] }
|
||||
driver
|
||||
end
|
||||
|
||||
let :key do
|
||||
@@ -40,14 +40,14 @@ describe WebSocket::Protocol::Client do
|
||||
end
|
||||
|
||||
before do
|
||||
WebSocket::Protocol::Client.stub(:generate_key).and_return(key)
|
||||
WebSocket::Driver::Client.stub(:generate_key).and_return(key)
|
||||
@open = @error = @close = false
|
||||
@message = ""
|
||||
end
|
||||
|
||||
describe "in the beginning state" do
|
||||
it "starts in no state" do
|
||||
protocol.state.should == nil
|
||||
driver.state.should == nil
|
||||
end
|
||||
|
||||
describe :start do
|
||||
@@ -60,11 +60,11 @@ describe WebSocket::Protocol::Client do
|
||||
"Sec-WebSocket-Key: 2vBVWg4Qyk3ZoM/5d3QD9Q==\r\n" +
|
||||
"Sec-WebSocket-Version: 13\r\n" +
|
||||
"\r\n")
|
||||
protocol.start
|
||||
driver.start
|
||||
end
|
||||
|
||||
it "returns true" do
|
||||
protocol.start.should == true
|
||||
driver.start.should == true
|
||||
end
|
||||
|
||||
describe "with subprotocols" do
|
||||
@@ -80,40 +80,40 @@ describe WebSocket::Protocol::Client do
|
||||
"Sec-WebSocket-Version: 13\r\n" +
|
||||
"Sec-WebSocket-Protocol: foo, bar, xmpp\r\n" +
|
||||
"\r\n")
|
||||
protocol.start
|
||||
driver.start
|
||||
end
|
||||
end
|
||||
|
||||
it "changes the state to :connecting" do
|
||||
protocol.start
|
||||
protocol.state.should == :connecting
|
||||
driver.start
|
||||
driver.state.should == :connecting
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "in the :connecting state" do
|
||||
before { protocol.start }
|
||||
before { driver.start }
|
||||
|
||||
describe "with a valid response" do
|
||||
before { protocol.parse(response) }
|
||||
before { driver.parse(response) }
|
||||
|
||||
it "changes the state to :open" do
|
||||
@open.should == true
|
||||
@close.should == false
|
||||
protocol.state.should == :open
|
||||
driver.state.should == :open
|
||||
end
|
||||
end
|
||||
|
||||
describe "with a valid response followed by a frame" do
|
||||
before do
|
||||
resp = response + WebSocket::Protocol.encode([0x81, 0x02, 72, 105])
|
||||
protocol.parse(resp)
|
||||
resp = response + WebSocket::Driver.encode([0x81, 0x02, 72, 105])
|
||||
driver.parse(resp)
|
||||
end
|
||||
|
||||
it "changes the state to :open" do
|
||||
@open.should == true
|
||||
@close.should == false
|
||||
protocol.state.should == :open
|
||||
driver.state.should == :open
|
||||
end
|
||||
|
||||
it "parses the frame" do
|
||||
@@ -124,28 +124,28 @@ describe WebSocket::Protocol::Client do
|
||||
describe "with a bad Upgrade header" do
|
||||
before do
|
||||
resp = response.gsub(/websocket/, "wrong")
|
||||
protocol.parse(resp)
|
||||
driver.parse(resp)
|
||||
end
|
||||
|
||||
it "changes the state to :closed" do
|
||||
@open.should == false
|
||||
@error.message.should == "Error during WebSocket handshake: 'Upgrade' header value is not 'WebSocket'"
|
||||
@close.should == [1002, "Error during WebSocket handshake: 'Upgrade' header value is not 'WebSocket'"]
|
||||
protocol.state.should == :closed
|
||||
driver.state.should == :closed
|
||||
end
|
||||
end
|
||||
|
||||
describe "with a bad Accept header" do
|
||||
before do
|
||||
resp = response.gsub(/QV3/, "wrong")
|
||||
protocol.parse(resp)
|
||||
driver.parse(resp)
|
||||
end
|
||||
|
||||
it "changes the state to :closed" do
|
||||
@open.should == false
|
||||
@error.message.should == "Error during WebSocket handshake: Sec-WebSocket-Accept mismatch"
|
||||
@close.should == [1002, "Error during WebSocket handshake: Sec-WebSocket-Accept mismatch"]
|
||||
protocol.state.should == :closed
|
||||
driver.state.should == :closed
|
||||
end
|
||||
end
|
||||
|
||||
@@ -154,17 +154,17 @@ describe WebSocket::Protocol::Client do
|
||||
|
||||
before do
|
||||
resp = response.gsub(/\r\n\r\n/, "\r\nSec-WebSocket-Protocol: xmpp\r\n\r\n")
|
||||
protocol.parse(resp)
|
||||
driver.parse(resp)
|
||||
end
|
||||
|
||||
it "changes the state to :open" do
|
||||
@open.should == true
|
||||
@close.should == false
|
||||
protocol.state.should == :open
|
||||
driver.state.should == :open
|
||||
end
|
||||
|
||||
it "selects the subprotocol" do
|
||||
protocol.protocol.should == "xmpp"
|
||||
driver.protocol.should == "xmpp"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -173,18 +173,18 @@ describe WebSocket::Protocol::Client do
|
||||
|
||||
before do
|
||||
resp = response.gsub(/\r\n\r\n/, "\r\nSec-WebSocket-Protocol: irc\r\n\r\n")
|
||||
protocol.parse(resp)
|
||||
driver.parse(resp)
|
||||
end
|
||||
|
||||
it "changes the state to :closed" do
|
||||
@open.should == false
|
||||
@error.message.should == "Error during WebSocket handshake: Sec-WebSocket-Protocol mismatch"
|
||||
@close.should == [1002, "Error during WebSocket handshake: Sec-WebSocket-Protocol mismatch"]
|
||||
protocol.state.should == :closed
|
||||
driver.state.should == :closed
|
||||
end
|
||||
|
||||
it "selects no subprotocol" do
|
||||
protocol.protocol.should == nil
|
||||
driver.protocol.should == nil
|
||||
end
|
||||
end
|
||||
end
|
||||
+25
-25
@@ -4,111 +4,111 @@ require "spec_helper"
|
||||
|
||||
shared_examples_for "draft-75 protocol" do
|
||||
describe "in the :open state" do
|
||||
before { protocol.start }
|
||||
before { driver.start }
|
||||
|
||||
describe :parse do
|
||||
it "parses text frames" do
|
||||
protocol.parse [0x00, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0xff]
|
||||
driver.parse [0x00, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0xff]
|
||||
@message.should == "Hello"
|
||||
end
|
||||
|
||||
it "parses multiple frames from the same packet" do
|
||||
protocol.parse [0x00, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0xff, 0x00, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0xff]
|
||||
driver.parse [0x00, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0xff, 0x00, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0xff]
|
||||
@message.should == "HelloHello"
|
||||
end
|
||||
|
||||
it "parses text frames beginning 0x00-0x7F" do
|
||||
protocol.parse [0x66, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0xff]
|
||||
driver.parse [0x66, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0xff]
|
||||
@message.should == "Hello"
|
||||
end
|
||||
|
||||
it "ignores frames with a length header" do
|
||||
protocol.parse [0x80, 0x02, 0x48, 0x65, 0x00, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0xff]
|
||||
driver.parse [0x80, 0x02, 0x48, 0x65, 0x00, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0xff]
|
||||
@message.should == "Hello"
|
||||
end
|
||||
|
||||
it "parses multibyte text frames" do
|
||||
protocol.parse [0x00, 0x41, 0x70, 0x70, 0x6c, 0x65, 0x20, 0x3d, 0x20, 0xef, 0xa3, 0xbf, 0xff]
|
||||
driver.parse [0x00, 0x41, 0x70, 0x70, 0x6c, 0x65, 0x20, 0x3d, 0x20, 0xef, 0xa3, 0xbf, 0xff]
|
||||
@message.should == encode("Apple = ")
|
||||
end
|
||||
|
||||
it "parses frames received in several packets" do
|
||||
protocol.parse [0x00, 0x41, 0x70, 0x70, 0x6c, 0x65]
|
||||
protocol.parse [0x20, 0x3d, 0x20, 0xef, 0xa3, 0xbf, 0xff]
|
||||
driver.parse [0x00, 0x41, 0x70, 0x70, 0x6c, 0x65]
|
||||
driver.parse [0x20, 0x3d, 0x20, 0xef, 0xa3, 0xbf, 0xff]
|
||||
@message.should == encode("Apple = ")
|
||||
end
|
||||
|
||||
it "parses fragmented frames" do
|
||||
protocol.parse [0x00, 0x48, 0x65, 0x6c]
|
||||
protocol.parse [0x6c, 0x6f, 0xff]
|
||||
driver.parse [0x00, 0x48, 0x65, 0x6c]
|
||||
driver.parse [0x6c, 0x6f, 0xff]
|
||||
@message.should == "Hello"
|
||||
end
|
||||
end
|
||||
|
||||
describe :frame do
|
||||
it "formats the given string as a WebSocket frame" do
|
||||
protocol.frame "Hello"
|
||||
driver.frame "Hello"
|
||||
@bytes.should == [0x00, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0xff]
|
||||
end
|
||||
|
||||
it "encodes multibyte characters correctly" do
|
||||
message = encode "Apple = "
|
||||
protocol.frame message
|
||||
driver.frame message
|
||||
@bytes.should == [0x00, 0x41, 0x70, 0x70, 0x6c, 0x65, 0x20, 0x3d, 0x20, 0xef, 0xa3, 0xbf, 0xff]
|
||||
end
|
||||
|
||||
it "returns true" do
|
||||
protocol.frame("lol").should == true
|
||||
driver.frame("lol").should == true
|
||||
end
|
||||
end
|
||||
|
||||
describe :ping do
|
||||
it "does not write to the socket" do
|
||||
socket.should_not_receive(:write)
|
||||
protocol.ping
|
||||
driver.ping
|
||||
end
|
||||
|
||||
it "returns false" do
|
||||
protocol.ping.should == false
|
||||
driver.ping.should == false
|
||||
end
|
||||
end
|
||||
|
||||
describe :close do
|
||||
it "triggers the onclose event" do
|
||||
protocol.close
|
||||
driver.close
|
||||
@close.should == true
|
||||
end
|
||||
|
||||
it "returns true" do
|
||||
protocol.close.should == true
|
||||
driver.close.should == true
|
||||
end
|
||||
|
||||
it "changes the state to :closed" do
|
||||
protocol.close
|
||||
protocol.state.should == :closed
|
||||
driver.close
|
||||
driver.state.should == :closed
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "in the :closed state" do
|
||||
before do
|
||||
protocol.start
|
||||
protocol.close
|
||||
driver.start
|
||||
driver.close
|
||||
end
|
||||
|
||||
describe :close do
|
||||
it "does not write to the socket" do
|
||||
socket.should_not_receive(:write)
|
||||
protocol.close
|
||||
driver.close
|
||||
end
|
||||
|
||||
it "returns false" do
|
||||
protocol.close.should == false
|
||||
driver.close.should == false
|
||||
end
|
||||
|
||||
it "leaves the protocol in the :closed state" do
|
||||
protocol.close
|
||||
protocol.state.should == :closed
|
||||
driver.close
|
||||
driver.state.should == :closed
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
require "spec_helper"
|
||||
|
||||
describe WebSocket::Protocol::Draft75 do
|
||||
describe WebSocket::Driver::Draft75 do
|
||||
include EncodingHelper
|
||||
|
||||
let :env do
|
||||
@@ -22,12 +22,12 @@ describe WebSocket::Protocol::Draft75 do
|
||||
socket
|
||||
end
|
||||
|
||||
let :protocol do
|
||||
protocol = WebSocket::Protocol::Draft75.new(socket)
|
||||
protocol.on(:open) { |e| @open = true }
|
||||
protocol.on(:message) { |e| @message += e.data }
|
||||
protocol.on(:close) { |e| @close = true }
|
||||
protocol
|
||||
let :driver do
|
||||
driver = WebSocket::Driver::Draft75.new(socket)
|
||||
driver.on(:open) { |e| @open = true }
|
||||
driver.on(:message) { |e| @message += e.data }
|
||||
driver.on(:close) { |e| @close = true }
|
||||
driver
|
||||
end
|
||||
|
||||
before do
|
||||
@@ -37,7 +37,7 @@ describe WebSocket::Protocol::Draft75 do
|
||||
|
||||
describe "in the :connecting state" do
|
||||
it "starts in the :connecting state" do
|
||||
protocol.state.should == :connecting
|
||||
driver.state.should == :connecting
|
||||
end
|
||||
|
||||
describe :start do
|
||||
@@ -49,37 +49,37 @@ describe WebSocket::Protocol::Draft75 do
|
||||
"WebSocket-Origin: http://www.example.com\r\n" +
|
||||
"WebSocket-Location: ws://www.example.com/socket\r\n" +
|
||||
"\r\n")
|
||||
protocol.start
|
||||
driver.start
|
||||
end
|
||||
|
||||
it "returns true" do
|
||||
protocol.start.should == true
|
||||
driver.start.should == true
|
||||
end
|
||||
|
||||
it "triggers the onopen event" do
|
||||
protocol.start
|
||||
driver.start
|
||||
@open.should == true
|
||||
end
|
||||
|
||||
it "changes the state to :open" do
|
||||
protocol.start
|
||||
protocol.state.should == :open
|
||||
driver.start
|
||||
driver.state.should == :open
|
||||
end
|
||||
|
||||
it "sets the protocol version" do
|
||||
protocol.start
|
||||
protocol.version.should == "hixie-75"
|
||||
driver.start
|
||||
driver.version.should == "hixie-75"
|
||||
end
|
||||
end
|
||||
|
||||
describe :frame do
|
||||
it "does not write to the socket" do
|
||||
socket.should_not_receive(:write)
|
||||
protocol.frame("Hello, world")
|
||||
driver.frame("Hello, world")
|
||||
end
|
||||
|
||||
it "returns true" do
|
||||
protocol.frame("whatever").should == true
|
||||
driver.frame("whatever").should == true
|
||||
end
|
||||
|
||||
it "queues the frames until the handshake has been sent" do
|
||||
@@ -92,8 +92,8 @@ describe WebSocket::Protocol::Draft75 do
|
||||
"\r\n")
|
||||
socket.should_receive(:write).with("\x00Hi\xFF")
|
||||
|
||||
protocol.frame("Hi")
|
||||
protocol.start
|
||||
driver.frame("Hi")
|
||||
driver.start
|
||||
|
||||
@bytes.should == [0x00, 72, 105, 0xFF]
|
||||
end
|
||||
@@ -2,11 +2,11 @@
|
||||
|
||||
require "spec_helper"
|
||||
|
||||
describe WebSocket::Protocol::Draft76 do
|
||||
describe WebSocket::Driver::Draft76 do
|
||||
include EncodingHelper
|
||||
|
||||
let :body do
|
||||
WebSocket::Protocol.encode [0x91, 0x25, 0x3e, 0xd3, 0xa9, 0xe7, 0x6a, 0x88]
|
||||
WebSocket::Driver.encode [0x91, 0x25, 0x3e, 0xd3, 0xa9, 0xe7, 0x6a, 0x88]
|
||||
end
|
||||
|
||||
let :response do
|
||||
@@ -35,12 +35,12 @@ describe WebSocket::Protocol::Draft76 do
|
||||
socket
|
||||
end
|
||||
|
||||
let :protocol do
|
||||
protocol = WebSocket::Protocol::Draft76.new(socket)
|
||||
protocol.on(:open) { |e| @open = true }
|
||||
protocol.on(:message) { |e| @message += e.data }
|
||||
protocol.on(:close) { |e| @close = true }
|
||||
protocol
|
||||
let :driver do
|
||||
driver = WebSocket::Driver::Draft76.new(socket)
|
||||
driver.on(:open) { |e| @open = true }
|
||||
driver.on(:message) { |e| @message += e.data }
|
||||
driver.on(:close) { |e| @close = true }
|
||||
driver
|
||||
end
|
||||
|
||||
before do
|
||||
@@ -50,7 +50,7 @@ describe WebSocket::Protocol::Draft76 do
|
||||
|
||||
describe "in the :connecting state" do
|
||||
it "starts in the connecting state" do
|
||||
protocol.state.should == :connecting
|
||||
driver.state.should == :connecting
|
||||
end
|
||||
|
||||
describe :start do
|
||||
@@ -63,37 +63,37 @@ describe WebSocket::Protocol::Draft76 do
|
||||
"Sec-WebSocket-Location: ws://www.example.com/socket\r\n" +
|
||||
"\r\n")
|
||||
socket.should_receive(:write).with(response)
|
||||
protocol.start
|
||||
driver.start
|
||||
end
|
||||
|
||||
it "returns true" do
|
||||
protocol.start.should == true
|
||||
driver.start.should == true
|
||||
end
|
||||
|
||||
it "triggers the onopen event" do
|
||||
protocol.start
|
||||
driver.start
|
||||
@open.should == true
|
||||
end
|
||||
|
||||
it "changes the state to :open" do
|
||||
protocol.start
|
||||
protocol.state.should == :open
|
||||
driver.start
|
||||
driver.state.should == :open
|
||||
end
|
||||
|
||||
it "sets the protocol version" do
|
||||
protocol.start
|
||||
protocol.version.should == "hixie-76"
|
||||
driver.start
|
||||
driver.version.should == "hixie-76"
|
||||
end
|
||||
end
|
||||
|
||||
describe :frame do
|
||||
it "does not write to the socket" do
|
||||
socket.should_not_receive(:write)
|
||||
protocol.frame("Hello, world")
|
||||
driver.frame("Hello, world")
|
||||
end
|
||||
|
||||
it "returns true" do
|
||||
protocol.frame("whatever").should == true
|
||||
driver.frame("whatever").should == true
|
||||
end
|
||||
|
||||
it "queues the frames until the handshake has been sent" do
|
||||
@@ -107,8 +107,8 @@ describe WebSocket::Protocol::Draft76 do
|
||||
socket.should_receive(:write).with(response)
|
||||
socket.should_receive(:write).with("\x00Hi\xFF")
|
||||
|
||||
protocol.frame("Hi")
|
||||
protocol.start
|
||||
driver.frame("Hi")
|
||||
driver.start
|
||||
|
||||
@bytes.should == [0x00, 72, 105, 0xff]
|
||||
end
|
||||
@@ -126,42 +126,42 @@ describe WebSocket::Protocol::Draft76 do
|
||||
"Sec-WebSocket-Origin: http://www.example.com\r\n" +
|
||||
"Sec-WebSocket-Location: ws://www.example.com/socket\r\n" +
|
||||
"\r\n")
|
||||
protocol.start
|
||||
driver.start
|
||||
end
|
||||
|
||||
it "does not trigger the onopen event" do
|
||||
protocol.start
|
||||
driver.start
|
||||
@open.should == false
|
||||
end
|
||||
|
||||
it "leaves the protocol in the :connecting state" do
|
||||
protocol.start
|
||||
protocol.state.should == :connecting
|
||||
driver.start
|
||||
driver.state.should == :connecting
|
||||
end
|
||||
|
||||
describe "when the request body is received" do
|
||||
before { protocol.start }
|
||||
before { driver.start }
|
||||
|
||||
it "sends the response body" do
|
||||
socket.should_receive(:write).with(response)
|
||||
protocol.parse(body)
|
||||
driver.parse(body)
|
||||
end
|
||||
|
||||
it "triggers the onopen event" do
|
||||
protocol.parse(body)
|
||||
driver.parse(body)
|
||||
@open.should == true
|
||||
end
|
||||
|
||||
it "changes the state to :open" do
|
||||
protocol.parse(body)
|
||||
protocol.state.should == :open
|
||||
driver.parse(body)
|
||||
driver.state.should == :open
|
||||
end
|
||||
|
||||
it "sends any frames queued before the handshake was complete" do
|
||||
socket.should_receive(:write).with(response)
|
||||
socket.should_receive(:write).with("\x00hello\xFF")
|
||||
protocol.frame("hello")
|
||||
protocol.parse(body)
|
||||
driver.frame("hello")
|
||||
driver.parse(body)
|
||||
@bytes.should == [0, 104, 101, 108, 108, 111, 255]
|
||||
end
|
||||
end
|
||||
@@ -172,19 +172,19 @@ describe WebSocket::Protocol::Draft76 do
|
||||
it_should_behave_like "draft-75 protocol"
|
||||
|
||||
describe "in the :open state" do
|
||||
before { protocol.start }
|
||||
before { driver.start }
|
||||
|
||||
describe :parse do
|
||||
it "closes the socket if a close frame is received" do
|
||||
protocol.parse [0xff, 0x00]
|
||||
driver.parse [0xff, 0x00]
|
||||
@close.should == true
|
||||
protocol.state.should == :closed
|
||||
driver.state.should == :closed
|
||||
end
|
||||
end
|
||||
|
||||
describe :close do
|
||||
it "writes a close message to the socket" do
|
||||
protocol.close
|
||||
driver.close
|
||||
@bytes.should == [0xff, 0x00]
|
||||
end
|
||||
end
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
require "spec_helper"
|
||||
|
||||
describe WebSocket::Protocol::Hybi do
|
||||
describe WebSocket::Driver::Hybi do
|
||||
include EncodingHelper
|
||||
|
||||
let :env do
|
||||
@@ -28,13 +28,13 @@ describe WebSocket::Protocol::Hybi do
|
||||
socket
|
||||
end
|
||||
|
||||
let :protocol do
|
||||
protocol = WebSocket::Protocol::Hybi.new(socket, options)
|
||||
protocol.on(:open) { |e| @open = true }
|
||||
protocol.on(:message) { |e| @message += e.data }
|
||||
protocol.on(:error) { |e| @error = e }
|
||||
protocol.on(:close) { |e| @close = [e.code, e.reason] }
|
||||
protocol
|
||||
let :driver do
|
||||
driver = WebSocket::Driver::Hybi.new(socket, options)
|
||||
driver.on(:open) { |e| @open = true }
|
||||
driver.on(:message) { |e| @message += e.data }
|
||||
driver.on(:error) { |e| @error = e }
|
||||
driver.on(:close) { |e| @close = [e.code, e.reason] }
|
||||
driver
|
||||
end
|
||||
|
||||
before do
|
||||
@@ -44,7 +44,7 @@ describe WebSocket::Protocol::Hybi do
|
||||
|
||||
describe "in the :connecting state" do
|
||||
it "starts in the :connecting state" do
|
||||
protocol.state.should == :connecting
|
||||
driver.state.should == :connecting
|
||||
end
|
||||
|
||||
describe :start do
|
||||
@@ -55,11 +55,11 @@ describe WebSocket::Protocol::Hybi do
|
||||
"Connection: Upgrade\r\n" +
|
||||
"Sec-WebSocket-Accept: JdiiuafpBKRqD7eol0y4vJDTsTs=\r\n" +
|
||||
"\r\n")
|
||||
protocol.start
|
||||
driver.start
|
||||
end
|
||||
|
||||
it "returns true" do
|
||||
protocol.start.should == true
|
||||
driver.start.should == true
|
||||
end
|
||||
|
||||
describe "with subprotocols" do
|
||||
@@ -76,39 +76,39 @@ describe WebSocket::Protocol::Hybi do
|
||||
"Sec-WebSocket-Accept: JdiiuafpBKRqD7eol0y4vJDTsTs=\r\n" +
|
||||
"Sec-WebSocket-Protocol: xmpp\r\n" +
|
||||
"\r\n")
|
||||
protocol.start
|
||||
driver.start
|
||||
end
|
||||
|
||||
it "sets the subprotocol" do
|
||||
protocol.start
|
||||
protocol.protocol.should == "xmpp"
|
||||
driver.start
|
||||
driver.protocol.should == "xmpp"
|
||||
end
|
||||
end
|
||||
|
||||
it "triggers the onopen event" do
|
||||
protocol.start
|
||||
driver.start
|
||||
@open.should == true
|
||||
end
|
||||
|
||||
it "changes the state to :open" do
|
||||
protocol.start
|
||||
protocol.state.should == :open
|
||||
driver.start
|
||||
driver.state.should == :open
|
||||
end
|
||||
|
||||
it "sets the protocol version" do
|
||||
protocol.start
|
||||
protocol.version.should == "hybi-13"
|
||||
driver.start
|
||||
driver.version.should == "hybi-13"
|
||||
end
|
||||
end
|
||||
|
||||
describe :frame do
|
||||
it "does not write to the socket" do
|
||||
socket.should_not_receive(:write)
|
||||
protocol.frame("Hello, world")
|
||||
driver.frame("Hello, world")
|
||||
end
|
||||
|
||||
it "returns true" do
|
||||
protocol.frame("whatever").should == true
|
||||
driver.frame("whatever").should == true
|
||||
end
|
||||
|
||||
it "queues the frames until the handshake has been sent" do
|
||||
@@ -118,21 +118,21 @@ describe WebSocket::Protocol::Hybi do
|
||||
"Connection: Upgrade\r\n" +
|
||||
"Sec-WebSocket-Accept: JdiiuafpBKRqD7eol0y4vJDTsTs=\r\n" +
|
||||
"\r\n")
|
||||
socket.should_receive(:write).with(WebSocket::Protocol.encode [0x81, 0x02, 72, 105])
|
||||
socket.should_receive(:write).with(WebSocket::Driver.encode [0x81, 0x02, 72, 105])
|
||||
|
||||
protocol.frame("Hi")
|
||||
protocol.start
|
||||
driver.frame("Hi")
|
||||
driver.start
|
||||
end
|
||||
end
|
||||
|
||||
describe :ping do
|
||||
it "does not write to the socket" do
|
||||
socket.should_not_receive(:write)
|
||||
protocol.ping
|
||||
driver.ping
|
||||
end
|
||||
|
||||
it "returns true" do
|
||||
protocol.ping.should == true
|
||||
driver.ping.should == true
|
||||
end
|
||||
|
||||
it "queues the ping until the handshake has been sent" do
|
||||
@@ -142,37 +142,37 @@ describe WebSocket::Protocol::Hybi do
|
||||
"Connection: Upgrade\r\n" +
|
||||
"Sec-WebSocket-Accept: JdiiuafpBKRqD7eol0y4vJDTsTs=\r\n" +
|
||||
"\r\n")
|
||||
socket.should_receive(:write).with(WebSocket::Protocol.encode [0x89, 0])
|
||||
socket.should_receive(:write).with(WebSocket::Driver.encode [0x89, 0])
|
||||
|
||||
protocol.ping
|
||||
protocol.start
|
||||
driver.ping
|
||||
driver.start
|
||||
end
|
||||
end
|
||||
|
||||
describe :close do
|
||||
it "does not write anything to the socket" do
|
||||
socket.should_not_receive(:write)
|
||||
protocol.close
|
||||
driver.close
|
||||
end
|
||||
|
||||
it "returns true" do
|
||||
protocol.close.should == true
|
||||
driver.close.should == true
|
||||
end
|
||||
|
||||
it "triggers the onclose event" do
|
||||
protocol.close
|
||||
driver.close
|
||||
@close.should == [1000, ""]
|
||||
end
|
||||
|
||||
it "changes the state to :closed" do
|
||||
protocol.close
|
||||
protocol.state.should == :closed
|
||||
driver.close
|
||||
driver.state.should == :closed
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "in the :open state" do
|
||||
before { protocol.start }
|
||||
before { driver.start }
|
||||
|
||||
describe :parse do
|
||||
let(:mask) { (1..4).map { rand 255 } }
|
||||
@@ -186,181 +186,181 @@ describe WebSocket::Protocol::Hybi do
|
||||
end
|
||||
|
||||
it "parses unmasked text frames" do
|
||||
protocol.parse [0x81, 0x05, 0x48, 0x65, 0x6c, 0x6c, 0x6f]
|
||||
driver.parse [0x81, 0x05, 0x48, 0x65, 0x6c, 0x6c, 0x6f]
|
||||
@message.should == "Hello"
|
||||
end
|
||||
|
||||
it "parses multiple frames from the same packet" do
|
||||
protocol.parse [0x81, 0x05, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x81, 0x05, 0x48, 0x65, 0x6c, 0x6c, 0x6f]
|
||||
driver.parse [0x81, 0x05, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x81, 0x05, 0x48, 0x65, 0x6c, 0x6c, 0x6f]
|
||||
@message.should == "HelloHello"
|
||||
end
|
||||
|
||||
it "parses empty text frames" do
|
||||
protocol.parse [0x81, 0x00]
|
||||
driver.parse [0x81, 0x00]
|
||||
@message.should == ""
|
||||
end
|
||||
|
||||
it "parses fragmented text frames" do
|
||||
protocol.parse [0x01, 0x03, 0x48, 0x65, 0x6c]
|
||||
protocol.parse [0x80, 0x02, 0x6c, 0x6f]
|
||||
driver.parse [0x01, 0x03, 0x48, 0x65, 0x6c]
|
||||
driver.parse [0x80, 0x02, 0x6c, 0x6f]
|
||||
@message.should == "Hello"
|
||||
end
|
||||
|
||||
it "parses masked text frames" do
|
||||
protocol.parse [0x81, 0x85] + mask + mask_message(0x48, 0x65, 0x6c, 0x6c, 0x6f)
|
||||
driver.parse [0x81, 0x85] + mask + mask_message(0x48, 0x65, 0x6c, 0x6c, 0x6f)
|
||||
@message.should == "Hello"
|
||||
end
|
||||
|
||||
it "parses masked empty text frames" do
|
||||
protocol.parse [0x81, 0x80] + mask + mask_message()
|
||||
driver.parse [0x81, 0x80] + mask + mask_message()
|
||||
@message.should == ""
|
||||
end
|
||||
|
||||
it "parses masked fragmented text frames" do
|
||||
protocol.parse [0x01, 0x81] + mask + mask_message(0x48)
|
||||
protocol.parse [0x80, 0x84] + mask + mask_message(0x65, 0x6c, 0x6c, 0x6f)
|
||||
driver.parse [0x01, 0x81] + mask + mask_message(0x48)
|
||||
driver.parse [0x80, 0x84] + mask + mask_message(0x65, 0x6c, 0x6c, 0x6f)
|
||||
@message.should == "Hello"
|
||||
end
|
||||
|
||||
it "closes the socket if the frame has an unrecognized opcode" do
|
||||
protocol.parse [0x83, 0x00]
|
||||
driver.parse [0x83, 0x00]
|
||||
@bytes[0..3].should == [0x88, 0x1e, 0x03, 0xea]
|
||||
@error.message.should == "Unrecognized frame opcode: 3"
|
||||
@close.should == [1002, "Unrecognized frame opcode: 3"]
|
||||
protocol.state.should == :closed
|
||||
driver.state.should == :closed
|
||||
end
|
||||
|
||||
it "closes the socket if a close frame is received" do
|
||||
protocol.parse [0x88, 0x07, 0x03, 0xe8, 0x48, 0x65, 0x6c, 0x6c, 0x6f]
|
||||
driver.parse [0x88, 0x07, 0x03, 0xe8, 0x48, 0x65, 0x6c, 0x6c, 0x6f]
|
||||
@bytes.should == [0x88, 0x07, 0x03, 0xe8, 0x48, 0x65, 0x6c, 0x6c, 0x6f]
|
||||
@close.should == [1000, "Hello"]
|
||||
protocol.state.should == :closed
|
||||
driver.state.should == :closed
|
||||
end
|
||||
|
||||
it "parses unmasked multibyte text frames" do
|
||||
protocol.parse [0x81, 0x0b, 0x41, 0x70, 0x70, 0x6c, 0x65, 0x20, 0x3d, 0x20, 0xef, 0xa3, 0xbf]
|
||||
driver.parse [0x81, 0x0b, 0x41, 0x70, 0x70, 0x6c, 0x65, 0x20, 0x3d, 0x20, 0xef, 0xa3, 0xbf]
|
||||
@message.should == encode("Apple = ")
|
||||
end
|
||||
|
||||
it "parses frames received in several packets" do
|
||||
protocol.parse [0x81, 0x0b, 0x41, 0x70, 0x70, 0x6c]
|
||||
protocol.parse [0x65, 0x20, 0x3d, 0x20, 0xef, 0xa3, 0xbf]
|
||||
driver.parse [0x81, 0x0b, 0x41, 0x70, 0x70, 0x6c]
|
||||
driver.parse [0x65, 0x20, 0x3d, 0x20, 0xef, 0xa3, 0xbf]
|
||||
@message.should == encode("Apple = ")
|
||||
end
|
||||
|
||||
it "parses fragmented multibyte text frames" do
|
||||
protocol.parse [0x01, 0x0a, 0x41, 0x70, 0x70, 0x6c, 0x65, 0x20, 0x3d, 0x20, 0xef, 0xa3]
|
||||
protocol.parse [0x80, 0x01, 0xbf]
|
||||
driver.parse [0x01, 0x0a, 0x41, 0x70, 0x70, 0x6c, 0x65, 0x20, 0x3d, 0x20, 0xef, 0xa3]
|
||||
driver.parse [0x80, 0x01, 0xbf]
|
||||
@message.should == encode("Apple = ")
|
||||
end
|
||||
|
||||
it "parses masked multibyte text frames" do
|
||||
protocol.parse [0x81, 0x8b] + mask + mask_message(0x41, 0x70, 0x70, 0x6c, 0x65, 0x20, 0x3d, 0x20, 0xef, 0xa3, 0xbf)
|
||||
driver.parse [0x81, 0x8b] + mask + mask_message(0x41, 0x70, 0x70, 0x6c, 0x65, 0x20, 0x3d, 0x20, 0xef, 0xa3, 0xbf)
|
||||
@message.should == encode("Apple = ")
|
||||
end
|
||||
|
||||
it "parses masked fragmented multibyte text frames" do
|
||||
protocol.parse [0x01, 0x8a] + mask + mask_message(0x41, 0x70, 0x70, 0x6c, 0x65, 0x20, 0x3d, 0x20, 0xef, 0xa3)
|
||||
protocol.parse [0x80, 0x81] + mask + mask_message(0xbf)
|
||||
driver.parse [0x01, 0x8a] + mask + mask_message(0x41, 0x70, 0x70, 0x6c, 0x65, 0x20, 0x3d, 0x20, 0xef, 0xa3)
|
||||
driver.parse [0x80, 0x81] + mask + mask_message(0xbf)
|
||||
@message.should == encode("Apple = ")
|
||||
end
|
||||
|
||||
it "parses unmasked medium-length text frames" do
|
||||
protocol.parse [0x81, 0x7e, 0x00, 0xc8] + [0x48, 0x65, 0x6c, 0x6c, 0x6f] * 40
|
||||
driver.parse [0x81, 0x7e, 0x00, 0xc8] + [0x48, 0x65, 0x6c, 0x6c, 0x6f] * 40
|
||||
@message.should == "Hello" * 40
|
||||
end
|
||||
|
||||
it "parses masked medium-length text frames" do
|
||||
protocol.parse [0x81, 0xfe, 0x00, 0xc8] + mask + mask_message(*([0x48, 0x65, 0x6c, 0x6c, 0x6f] * 40))
|
||||
driver.parse [0x81, 0xfe, 0x00, 0xc8] + mask + mask_message(*([0x48, 0x65, 0x6c, 0x6c, 0x6f] * 40))
|
||||
@message.should == "Hello" * 40
|
||||
end
|
||||
|
||||
it "replies to pings with a pong" do
|
||||
protocol.parse [0x89, 0x04, 0x4f, 0x48, 0x41, 0x49]
|
||||
driver.parse [0x89, 0x04, 0x4f, 0x48, 0x41, 0x49]
|
||||
@bytes.should == [0x8a, 0x04, 0x4f, 0x48, 0x41, 0x49]
|
||||
end
|
||||
end
|
||||
|
||||
describe :frame do
|
||||
it "formats the given string as a WebSocket frame" do
|
||||
protocol.frame "Hello"
|
||||
driver.frame "Hello"
|
||||
@bytes.should == [0x81, 0x05, 0x48, 0x65, 0x6c, 0x6c, 0x6f]
|
||||
end
|
||||
|
||||
it "formats a byte array as a binary WebSocket frame" do
|
||||
protocol.frame [0x48, 0x65, 0x6c]
|
||||
driver.frame [0x48, 0x65, 0x6c]
|
||||
@bytes.should == [0x82, 0x03, 0x48, 0x65, 0x6c]
|
||||
end
|
||||
|
||||
it "encodes multibyte characters correctly" do
|
||||
message = encode "Apple = "
|
||||
protocol.frame message
|
||||
driver.frame message
|
||||
@bytes.should == [0x81, 0x0b, 0x41, 0x70, 0x70, 0x6c, 0x65, 0x20, 0x3d, 0x20, 0xef, 0xa3, 0xbf]
|
||||
end
|
||||
|
||||
it "encodes medium-length strings using extra length bytes" do
|
||||
message = "Hello" * 40
|
||||
protocol.frame message
|
||||
driver.frame message
|
||||
@bytes.should == [0x81, 0x7e, 0x00, 0xc8] + [0x48, 0x65, 0x6c, 0x6c, 0x6f] * 40
|
||||
end
|
||||
|
||||
it "encodes close frames with an error code" do
|
||||
protocol.frame "Hello", :close, 1002
|
||||
driver.frame "Hello", :close, 1002
|
||||
@bytes.should == [0x88, 0x07, 0x03, 0xea, 0x48, 0x65, 0x6c, 0x6c, 0x6f]
|
||||
end
|
||||
|
||||
it "encodes pong frames" do
|
||||
protocol.frame "", :pong
|
||||
driver.frame "", :pong
|
||||
@bytes.should == [0x8a, 0x00]
|
||||
end
|
||||
end
|
||||
|
||||
describe :ping do
|
||||
it "writes a ping frame to the socket" do
|
||||
protocol.ping("mic check")
|
||||
driver.ping("mic check")
|
||||
@bytes.should == [0x89, 0x09, 0x6d, 0x69, 0x63, 0x20, 0x63, 0x68, 0x65, 0x63, 0x6b]
|
||||
end
|
||||
|
||||
it "returns true" do
|
||||
protocol.ping.should == true
|
||||
driver.ping.should == true
|
||||
end
|
||||
|
||||
it "runs the given callback on matching pong" do
|
||||
protocol.ping("Hi") { @reply = true }
|
||||
protocol.parse [0x8a, 0x02, 72, 105]
|
||||
driver.ping("Hi") { @reply = true }
|
||||
driver.parse [0x8a, 0x02, 72, 105]
|
||||
@reply.should == true
|
||||
end
|
||||
|
||||
it "does not run the callback on non-matching pong" do
|
||||
protocol.ping("Hi") { @reply = true }
|
||||
protocol.parse [0x8a, 0x03, 119, 97, 116]
|
||||
driver.ping("Hi") { @reply = true }
|
||||
driver.parse [0x8a, 0x03, 119, 97, 116]
|
||||
@reply.should == nil
|
||||
end
|
||||
end
|
||||
|
||||
describe :close do
|
||||
it "writes a close frame to the socket" do
|
||||
protocol.close("<%= reasons %>", 1003)
|
||||
driver.close("<%= reasons %>", 1003)
|
||||
@bytes.should == [0x88, 0x10, 0x03, 0xeb, 0x3c, 0x25, 0x3d, 0x20, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x73, 0x20, 0x25, 0x3e]
|
||||
end
|
||||
|
||||
it "returns true" do
|
||||
protocol.close.should == true
|
||||
driver.close.should == true
|
||||
end
|
||||
|
||||
it "does not trigger the onclose event" do
|
||||
protocol.close
|
||||
driver.close
|
||||
@close.should == false
|
||||
end
|
||||
|
||||
it "does not trigger the onerror event" do
|
||||
protocol.close
|
||||
driver.close
|
||||
@error.should == false
|
||||
end
|
||||
|
||||
it "changes the state to :closing" do
|
||||
protocol.close
|
||||
protocol.state.should == :closing
|
||||
driver.close
|
||||
driver.state.should == :closing
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -368,62 +368,62 @@ describe WebSocket::Protocol::Hybi do
|
||||
describe "when masking is required" do
|
||||
before do
|
||||
options[:require_masking] = true
|
||||
protocol.start
|
||||
driver.start
|
||||
end
|
||||
|
||||
it "does not emit a message" do
|
||||
protocol.parse [0x81, 0x05, 0x48, 0x65, 0x6c, 0x6c, 0x6f]
|
||||
driver.parse [0x81, 0x05, 0x48, 0x65, 0x6c, 0x6c, 0x6f]
|
||||
@message.should == ""
|
||||
end
|
||||
|
||||
it "returns an error" do
|
||||
protocol.parse [0x81, 0x05, 0x48, 0x65, 0x6c, 0x6c, 0x6f]
|
||||
driver.parse [0x81, 0x05, 0x48, 0x65, 0x6c, 0x6c, 0x6f]
|
||||
@close.should == [1003, "Received unmasked frame but masking is required"]
|
||||
end
|
||||
end
|
||||
|
||||
describe "in the :closing state" do
|
||||
before do
|
||||
protocol.start
|
||||
protocol.close
|
||||
driver.start
|
||||
driver.close
|
||||
end
|
||||
|
||||
describe :frame do
|
||||
it "does not write to the socket" do
|
||||
socket.should_not_receive(:write)
|
||||
protocol.frame("dropped")
|
||||
driver.frame("dropped")
|
||||
end
|
||||
|
||||
it "returns false" do
|
||||
protocol.frame("wut").should == false
|
||||
driver.frame("wut").should == false
|
||||
end
|
||||
end
|
||||
|
||||
describe :ping do
|
||||
it "does not write to the socket" do
|
||||
socket.should_not_receive(:write)
|
||||
protocol.ping
|
||||
driver.ping
|
||||
end
|
||||
|
||||
it "returns false" do
|
||||
protocol.ping.should == false
|
||||
driver.ping.should == false
|
||||
end
|
||||
end
|
||||
|
||||
describe :close do
|
||||
it "does not write to the socket" do
|
||||
socket.should_not_receive(:write)
|
||||
protocol.close
|
||||
driver.close
|
||||
end
|
||||
|
||||
it "returns false" do
|
||||
protocol.close.should == false
|
||||
driver.close.should == false
|
||||
end
|
||||
end
|
||||
|
||||
describe "receiving a close frame" do
|
||||
before do
|
||||
protocol.parse [0x88, 0x04, 0x03, 0xe9, 0x4f, 0x4b]
|
||||
driver.parse [0x88, 0x04, 0x03, 0xe9, 0x4f, 0x4b]
|
||||
end
|
||||
|
||||
it "triggers the onclose event" do
|
||||
@@ -431,53 +431,53 @@ describe WebSocket::Protocol::Hybi do
|
||||
end
|
||||
|
||||
it "changes the state to :closed" do
|
||||
protocol.state.should == :closed
|
||||
driver.state.should == :closed
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "in the :closed state" do
|
||||
before do
|
||||
protocol.start
|
||||
protocol.close
|
||||
protocol.parse [0x88, 0x02, 0x03, 0xea]
|
||||
driver.start
|
||||
driver.close
|
||||
driver.parse [0x88, 0x02, 0x03, 0xea]
|
||||
end
|
||||
|
||||
describe :frame do
|
||||
it "does not write to the socket" do
|
||||
socket.should_not_receive(:write)
|
||||
protocol.frame("dropped")
|
||||
driver.frame("dropped")
|
||||
end
|
||||
|
||||
it "returns false" do
|
||||
protocol.frame("wut").should == false
|
||||
driver.frame("wut").should == false
|
||||
end
|
||||
end
|
||||
|
||||
describe :ping do
|
||||
it "does not write to the socket" do
|
||||
socket.should_not_receive(:write)
|
||||
protocol.ping
|
||||
driver.ping
|
||||
end
|
||||
|
||||
it "returns false" do
|
||||
protocol.ping.should == false
|
||||
driver.ping.should == false
|
||||
end
|
||||
end
|
||||
|
||||
describe :close do
|
||||
it "does not write to the socket" do
|
||||
socket.should_not_receive(:write)
|
||||
protocol.close
|
||||
driver.close
|
||||
end
|
||||
|
||||
it "returns false" do
|
||||
protocol.close.should == false
|
||||
driver.close.should == false
|
||||
end
|
||||
|
||||
it "leaves the state as :closed" do
|
||||
protocol.close
|
||||
protocol.state.should == :closed
|
||||
driver.close
|
||||
driver.state.should == :closed
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,10 +1,10 @@
|
||||
Gem::Specification.new do |s|
|
||||
s.name = 'websocket-protocol'
|
||||
s.name = 'websocket-driver'
|
||||
s.version = '0.1.0'
|
||||
s.summary = 'WebSocket protocol handler with pluggable I/O'
|
||||
s.author = 'James Coglan'
|
||||
s.email = 'jcoglan@gmail.com'
|
||||
s.homepage = 'http://github.com/faye/websocket-protocol-ruby'
|
||||
s.homepage = 'http://github.com/faye/websocket-driver-ruby'
|
||||
|
||||
s.extra_rdoc_files = %w[README.md]
|
||||
s.rdoc_options = %w[--main README.md --markup markdown]
|
||||
Reference in New Issue
Block a user