pax_global_header 0000666 0000000 0000000 00000000064 13711041666 0014517 g ustar 00root root 0000000 0000000 52 comment=622bc4368d474f4014ef2b4f537d496f2d83d430
faye-websocket-ruby-0.11.0/ 0000775 0000000 0000000 00000000000 13711041666 0015465 5 ustar 00root root 0000000 0000000 faye-websocket-ruby-0.11.0/.gitignore 0000664 0000000 0000000 00000000033 13711041666 0017451 0 ustar 00root root 0000000 0000000 Gemfile.lock
log
tmp
*.gem
faye-websocket-ruby-0.11.0/.travis.yml 0000664 0000000 0000000 00000000457 13711041666 0017604 0 ustar 00root root 0000000 0000000 sudo: false
dist: trusty
language: ruby
rvm:
- 1.9.3
- 2.0.0
- 2.1.10
- 2.2.10
- 2.3.8
- 2.4.10
- 2.5.8
- 2.6.6
- 2.7.1
- jruby-9.0
- jruby-9.1
- jruby-9.2
before_install:
- '[[ "$(ruby --version)" != *"1.9.3"* ]] || gem update --system 2.4.8'
script:
- bundle exec rspec
faye-websocket-ruby-0.11.0/CHANGELOG.md 0000664 0000000 0000000 00000011162 13711041666 0017277 0 ustar 00root root 0000000 0000000 ### 0.11.0 / 2020-07-31
- Implement TLS certificate verification and enable it by default on client
connections
- Add a `:tls` option to the client with sub-fields `:root_cert_file` and
`:verify_peer` for configuring TLS verification
### 0.10.9 / 2019-06-13
- Use the EventMachine API rather than `IO#write` to write data; this uses the
event loop and avoids getting blocked by slow clients
### 0.10.8 / 2019-06-10
- In the case of a close timeout, don't block on waiting for writing to the
socket to complete
- Fix a race condition that caused a timeout not to be cancelled immediately
when the WebSocket is closed
- Change license from MIT to Apache 2.0
### 0.10.7 / 2017-02-22
- Emit an error if `EventMachine::Connection#unbind` is called with an error
### 0.10.6 / 2017-01-22
- Forcibly close the I/O stream after a timeout if the peer does not respond
after calling `close()`
### 0.10.5 / 2016-11-12
- Set the SNI hostname when making secure requests
### 0.10.4 / 2016-05-20
- Amend warnings issued when running with -W2
### 0.10.3 / 2016-02-24
- Use `PATH_INFO` and `QUERY_STRING` rather than the non-standard `REQUEST_URI`
from the Rack env
### 0.10.2 / 2015-11-26
- Fix the `headers` and `status` methods on `Client`, which were broken in the
last release
### 0.10.1 / 2015-11-06
- Make sure errors can be safely emitted if creating the driver fails
- Prevent a race condition when binding `EM.attach` to the socket
### 0.10.0 / 2015-07-08
- Add the standard `code` and `reason` parameters to the `close` method
### 0.9.2 / 2014-12-21
- Only emit `error` once, and don't emit it after `close`
### 0.9.1 / 2014-12-18
- Check that all options to the WebSocket constructor are recognized
### 0.9.0 / 2014-12-13
- Allow protocol extensions to be passed into websocket-extensions
### 0.8.0 / 2014-11-08
- Support connections via HTTP proxies
### 0.7.5 / 2014-10-04
- Allow sockets to be closed when they are in any state other than `CLOSED`
### 0.7.4 / 2014-07-06
- Stop using `define_method` to implement `Event` properties, since it blows the
method cache
- Stop setup errors masking URI errors in `Client#initialize`
- Make the Goliath adapter compatible with goliath-1.0.4.
### 0.7.3 / 2014-04-24
- Remove an unneeded method override in the `WebSocket` class
### 0.7.2 / 2013-12-29
- Fix WebSocket detection in cases where the web server does not produce an
`env`
### 0.7.1 / 2013-12-03
- Support the `max_length` websocket-driver option
- Expose a `message` property on `error` events
### 0.7.0 / 2013-09-09
- Allow the server to send custom headers with EventSource responses
### 0.6.3 / 2013-08-04
- Stop implicitly depending on Rack 1.4
### 0.6.2 / 2013-07-05
- Catch errors thrown by EventMachine and emit `error` and `close` events
### 0.6.1 / 2013-05-12
- Release a gem without log and pid files in it
### 0.6.0 / 2013-05-12
- Add support for custom headers
### 0.5.0 / 2013-05-05
- Extract the protocol handlers into the `websocket-driver` library
- Support the `rack.hijack` API
- Add support for Rainbows 4.5 and Puma
- Officially support JRuby and Rubinius
### 0.4.7 / 2013-02-14
- Emit the `close` event if TCP is closed before CLOSE frame is acked
- Treat the `Upgrade: websocket` header case-insensitively because of IE10
- Do not suppress headers in the Thin and Rainbows adapters unless the status is
`101`
### 0.4.6 / 2012-07-09
- Add `Connection: close` to EventSource response
### 0.4.5 / 2012-04-06
- Add WebSocket error code `1011`.
- Handle URLs with no path correctly by sending `GET /`
### 0.4.4 / 2012-03-16
- Fix installation on JRuby with a platform-specific gem
### 0.4.3 / 2012-03-12
- Make `extconf.rb` a no-op on JRuby
### 0.4.2 / 2012-03-09
- Port masking-function C extension to Java for JRuby
### 0.4.1 / 2012-02-26
- Treat anything other than an `Array` as a string when calling `send()`
- Fix error loading UTF-8 validation code on Ruby 1.9 with `-Ku` flag
### 0.4.0 / 2012-02-13
- Add `ping()` method to server-side `WebSocket` and `EventSource`
- Buffer `send()` calls until the draft-76 handshake is complete
### 0.3.0 / 2012-01-13
- Add support for `EventSource` connections
- Support the Thin, Rainbows and Goliath web servers
### 0.2.0 / 2011-12-21
- Add support for `Sec-WebSocket-Protocol` negotiation
- Support `hixie-76` close frames and 75/76 ignored segments
- Improve performance of HyBi parsing/framing functions
- Write masking function in C
### 0.1.2 / 2011-12-05
- Make `hixie-76` sockets work through HAProxy
### 0.1.1 / 2011-11-30
- Fix `add_event_listener()` interface methods
### 0.1.0 / 2011-11-27
- Initial release, based on WebSocket components from Faye
faye-websocket-ruby-0.11.0/CODE_OF_CONDUCT.md 0000664 0000000 0000000 00000000242 13711041666 0020262 0 ustar 00root root 0000000 0000000 # Code of Conduct
All projects under the [Faye](https://github.com/faye) umbrella are covered by
the [Code of Conduct](https://github.com/faye/code-of-conduct).
faye-websocket-ruby-0.11.0/Gemfile 0000664 0000000 0000000 00000000046 13711041666 0016760 0 ustar 00root root 0000000 0000000 source 'https://rubygems.org'
gemspec
faye-websocket-ruby-0.11.0/LICENSE.md 0000664 0000000 0000000 00000001056 13711041666 0017073 0 ustar 00root root 0000000 0000000 Copyright 2010-2020 James Coglan
Licensed under the Apache License, Version 2.0 (the "License"); you may not use
this file except in compliance with the License. You may obtain a copy of the
License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed
under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied. See the License for the
specific language governing permissions and limitations under the License.
faye-websocket-ruby-0.11.0/README.md 0000664 0000000 0000000 00000037403 13711041666 0016753 0 ustar 00root root 0000000 0000000 # faye-websocket [](http://travis-ci.org/faye/faye-websocket-ruby)
This is a general-purpose WebSocket implementation extracted from the
[Faye](http://faye.jcoglan.com) project. It provides classes for easily building
WebSocket servers and clients in Ruby. It does not provide a server itself, but
rather makes it easy to handle WebSocket connections within an existing
[Rack](http://rack.github.io/) application. It does not provide any abstraction
other than the standard [WebSocket
API](https://html.spec.whatwg.org/multipage/comms.html#network).
It also provides an abstraction for handling
[EventSource](https://html.spec.whatwg.org/multipage/comms.html#server-sent-events)
connections, which are one-way connections that allow the server to push data to
the client. They are based on streaming HTTP responses and can be easier to
access via proxies than WebSockets.
The following web servers are supported. Other servers that implement the
`rack.hijack` API should also work.
- [Goliath](http://postrank-labs.github.com/goliath/)
- [Phusion Passenger](https://www.phusionpassenger.com/) >= 4.0 with nginx >= 1.4
- [Puma](http://puma.io/) >= 2.0
- [Rainbows](http://rainbows.bogomips.org/)
- [Thin](http://code.macournoyer.com/thin/)
## Installation
```
$ gem install faye-websocket
```
## Handling WebSocket connections in Rack
You can handle WebSockets on the server side by listening for requests using the
`Faye::WebSocket.websocket?` method, and creating a new socket for the request.
This socket object exposes the usual WebSocket methods for receiving and sending
messages. For example this is how you'd implement an echo server:
```ruby
# app.rb
require 'faye/websocket'
App = lambda do |env|
if Faye::WebSocket.websocket?(env)
ws = Faye::WebSocket.new(env)
ws.on :message do |event|
ws.send(event.data)
end
ws.on :close do |event|
p [:close, event.code, event.reason]
ws = nil
end
# Return async Rack response
ws.rack_response
else
# Normal HTTP request
[200, { 'Content-Type' => 'text/plain' }, ['Hello']]
end
end
```
Note that under certain circumstances (notably a draft-76 client connecting
through an HTTP proxy), the WebSocket handshake will not be complete after you
call `Faye::WebSocket.new` because the server will not have received the entire
handshake from the client yet. In this case, calls to `ws.send` will buffer the
message in memory until the handshake is complete, at which point any buffered
messages will be sent to the client.
If you need to detect when the WebSocket handshake is complete, you can use the
`onopen` event.
If the connection's protocol version supports it, you can call `ws.ping()` to
send a ping message and wait for the client's response. This method takes a
message string, and an optional callback that fires when a matching pong message
is received. It returns `true` if and only if a ping message was sent. If the
client does not support ping/pong, this method sends no data and returns
`false`.
```ruby
ws.ping 'Mic check, one, two' do
# fires when pong is received
end
```
## Using the WebSocket client
The client supports both the plain-text `ws` protocol and the encrypted `wss`
protocol, and has exactly the same interface as a socket you would use in a web
browser. On the wire it identifies itself as `hybi-13`.
```ruby
require 'faye/websocket'
require 'eventmachine'
EM.run {
ws = Faye::WebSocket::Client.new('ws://www.example.com/')
ws.on :open do |event|
p [:open]
ws.send('Hello, world!')
end
ws.on :message do |event|
p [:message, event.data]
end
ws.on :close do |event|
p [:close, event.code, event.reason]
ws = nil
end
}
```
The WebSocket client also lets you inspect the status and headers of the
handshake response via its `status` and `headers` methods.
To connect via a proxy, set the `proxy` option to the HTTP origin of the proxy,
including any authorization information and custom headers you require:
```rb
ws = Faye::WebSocket::Client.new('ws://www.example.com/', [], {
:proxy => {
:origin => 'http://username:password@proxy.example.com',
:headers => { 'User-Agent' => 'ruby' }
}
})
```
## Subprotocol negotiation
The WebSocket protocol allows peers to select and identify the application
protocol to use over the connection. On the client side, you can set which
protocols the client accepts by passing a list of protocol names when you
construct the socket:
```ruby
ws = Faye::WebSocket::Client.new('ws://www.example.com/', ['irc', 'amqp'])
```
On the server side, you can likewise pass in the list of protocols the server
supports after the other constructor arguments:
```ruby
ws = Faye::WebSocket.new(env, ['irc', 'amqp'])
```
If the client and server agree on a protocol, both the client- and server-side
socket objects expose the selected protocol through the `ws.protocol` property.
## Protocol extensions
faye-websocket is based on the
[websocket-extensions](https://github.com/faye/websocket-extensions-ruby)
framework that allows extensions to be negotiated via the
`Sec-WebSocket-Extensions` header. To add extensions to a connection, pass an
array of extensions to the `:extensions` option. For example, to add
[permessage-deflate](https://github.com/faye/permessage-deflate-ruby):
```rb
require 'permessage_deflate'
ws = Faye::WebSocket.new(env, [], :extensions => [PermessageDeflate])
```
## Initialization options
Both the server- and client-side classes allow an options hash to be passed in
at initialization time, for example:
```ruby
ws = Faye::WebSocket.new(env, protocols, options)
ws = Faye::WebSocket::Client.new(url, protocols, options)
```
`protocols` as an array of subprotocols as described above, or `nil`. `options`
is an optional hash containing any of these keys:
- `:extensions` - an array of
[websocket-extensions](https://github.com/faye/websocket-extensions-ruby)
compatible extensions, as described above
- `:headers` - a hash containing key-value pairs representing HTTP headers to be
sent during the handshake process
- `:max_length` - the maximum allowed size of incoming message frames, in bytes.
The default value is `2^26 - 1`, or 1 byte short of 64 MiB.
- `:ping` - an integer that sets how often the WebSocket should send ping
frames, measured in seconds
- `:tls` - a hash containing key-value pairs for specifying TLS parameters.
These are passed along to EventMachine and you can find
[more details here](http://rubydoc.info/gems/eventmachine/EventMachine%2FConnection%3Astart_tls)
### Secure sockets
Starting with version 0.11.0, `Faye::WebSocket::Client` will verify the server
certificate for `wss` connections. This is not the default behaviour for
EventMachine's TLS interface, and so our defaults for the `:tls` option are a
little different.
First, `:verify_peer` is enabled by default. Our implementation checks that the
chain of certificates sent by the server is trusted by your root certificates,
and that the final certificate's hostname matches the hostname in the request
URL.
By default, we use your system's root certificate store by invoking
`OpenSSL::X509::Store#set_default_paths`. If you want to use a different set of
root certificates, you can pass them via the `:root_cert_file` option, which
takes a path or an array of paths to the certificates you want to use.
```ruby
ws = Faye::WebSocket::Client.new('wss://example.com/', [], :tls => {
:root_cert_file => ['path/to/certificate.pem']
})
```
If you want to switch off certificate verification altogether, then set
`:verify_peer` to `false`.
```ruby
ws = Faye::WebSocket::Client.new('wss://example.com/', [], :tls => {
:verify_peer => false
})
```
## WebSocket API
Both the server- and client-side `WebSocket` objects support the following API:
- **`on(:open) { |event| }`** fires when the socket connection is established.
Event has no attributes.
- **`on(:message) { |event| }`** fires when the socket receives a message. Event
has one attribute, **`data`**, which is either a `String` (for text frames) or
an `Array` of unsigned integers, i.e. integers in the range `0..255` (for
binary frames).
- **`on(:error) { |event| }`** fires when there is a protocol error due to bad
data sent by the other peer. This event is purely informational, you do not
need to implement error recovery.
- **`on(:close) { |event| }`** fires when either the client or the server closes
the connection. Event has two optional attributes, **`code`** and
**`reason`**, that expose the status code and message sent by the peer that
closed the connection.
- **`send(message)`** accepts either a `String` or an `Array` of byte-sized
integers and sends a text or binary message over the connection to the other
peer; binary data must be encoded as an `Array`.
- **`ping(message, &callback)`** sends a ping frame with an optional message and
fires the callback when a matching pong is received.
- **`close(code, reason)`** closes the connection, sending the given status code
and reason text, both of which are optional.
- **`version`** is a string containing the version of the `WebSocket` protocol
the connection is using.
- **`protocol`** is a string (which may be empty) identifying the subprotocol
the socket is using.
## Handling EventSource connections in Rack
EventSource connections provide a very similar interface, although because they
only allow the server to send data to the client, there is no `onmessage` API.
EventSource allows the server to push text messages to the client, where each
message has an optional event-type and ID.
```ruby
# app.rb
require 'faye/websocket'
App = lambda do |env|
if Faye::EventSource.eventsource?(env)
es = Faye::EventSource.new(env)
p [:open, es.url, es.last_event_id]
# Periodically send messages
loop = EM.add_periodic_timer(1) { es.send('Hello') }
es.on :close do |event|
EM.cancel_timer(loop)
es = nil
end
# Return async Rack response
es.rack_response
else
# Normal HTTP request
[200, { 'Content-Type' => 'text/plain' }, ['Hello']]
end
end
```
The `send` method takes two optional parameters, `:event` and `:id`. The default
event-type is `'message'` with no ID. For example, to send a `notification`
event with ID `99`:
```ruby
es.send('Breaking News!', :event => 'notification', :id => '99')
```
The `EventSource` object exposes the following properties:
- **`url`** is a string containing the URL the client used to create the
EventSource.
- **`last_event_id`** is a string containing the last event ID received by the
client. You can use this when the client reconnects after a dropped connection
to determine which messages need resending.
When you initialize an EventSource with `Faye::EventSource.new`, you can pass
configuration options after the `env` parameter. Available options are:
- **`:headers`** is a hash containing custom headers to be set on the
EventSource response.
- **`:retry`** is a number that tells the client how long (in seconds) it should
wait after a dropped connection before attempting to reconnect.
- **`:ping`** is a number that tells the server how often (in seconds) to send
'ping' packets to the client to keep the connection open, to defeat timeouts
set by proxies. The client will ignore these messages.
For example, this creates a connection that allows access from any origin, pings
every 15 seconds and is retryable every 10 seconds if the connection is broken:
```ruby
es = Faye::EventSource.new(es,
:headers => { 'Access-Control-Allow-Origin' => '*' },
:ping => 15,
:retry => 10
)
```
You can send a ping message at any time by calling `es.ping`. Unlike WebSocket
the client does not send a response to this; it is merely to send some data over
the wire to keep the connection alive.
## Running your socket application
The following describes how to run a WebSocket application using all our
supported web servers.
### Running the app with Thin
If you use Thin to serve your application you need to include this line after
loading `faye/websocket`:
```ruby
Faye::WebSocket.load_adapter('thin')
```
Thin can be started via the command line if you've set up a `config.ru` file for
your application:
```
$ thin start -R config.ru -p 9292
```
Or, you can use `rackup`. In development mode, this adds middlewares that don't
work with async apps, so you must start it in production mode:
```
$ rackup config.ru -s thin -E production -p 9292
```
It can also be started using the `Rack::Handler` interface common to many Ruby
servers. You can configure Thin further in a block passed to `run`:
```ruby
require 'eventmachine'
require 'rack'
require 'thin'
require './app'
Faye::WebSocket.load_adapter('thin')
thin = Rack::Handler.get('thin')
thin.run(App, :Port => 9292) do |server|
# You can set options on the server here, for example to set up SSL:
server.ssl_options = {
:private_key_file => 'path/to/ssl.key',
:cert_chain_file => 'path/to/ssl.crt'
}
server.ssl = true
end
```
### Running the app with Passenger
faye-websocket requires either Passenger for Nginx or Passenger Standalone.
[Apache doesn't work well with WebSockets at this time](https://github.com/phusion/passenger/issues/1202).
You do not need any special configuration to make faye-websocket work, it
should work out of the box on Passenger provided you use at least Passenger
4.0.
However, you do need to insert the following code in `config.ru` for optimal
WebSocket performance in Passenger. This is
[documented in the Passenger manual](https://www.phusionpassenger.com/documentation/Users%20guide%20Nginx.html#tuning_sse_websockets).
```ruby
if defined?(PhusionPassenger)
PhusionPassenger.advertised_concurrency_level = 0
end
```
Run your app on Passenger for Nginx by creating a virtual host entry which
points to your app's "public" directory:
```
server {
listen 9292;
server_name yourdomain.local;
root /path-to-your-app/public;
passenger_enabled on;
}
```
Or run your app on Passenger Standalone:
```
$ passenger start -p 9292
```
More information can be found on [the Passenger
website](https://www.phusionpassenger.com/support).
### Running the app with Puma
Puma has a command line interface for starting your application:
```
$ puma config.ru -p 9292
```
Or, you can use `rackup`. In development mode, this adds middlewares that don't
work with async apps, so you must start it in production mode:
```
$ rackup config.ru -s puma -E production -p 9292
```
### Running the app with Rainbows
If you're using version 4.4 or lower of Rainbows, you need to run it with the
EventMachine backend and enable the adapter. Put this in your `rainbows.conf`
file:
```ruby
Rainbows! { use :EventMachine }
```
And make sure you load the adapter in your application:
```ruby
Faye::WebSocket.load_adapter('rainbows')
```
Version 4.5 of Rainbows does not need this adapter.
You can run your `config.ru` file from the command line. Again, `Rack::Lint`
will complain unless you put the application in production mode.
```
$ rainbows config.ru -c path/to/rainbows.conf -E production -p 9292
```
### Running the app with Goliath
If you use Goliath to server your application you need to include this line
after loading `faye/websocket`:
```ruby
Faye::WebSocket.load_adapter('goliath')
```
Goliath can be made to run arbitrary Rack apps by delegating to them from a
`Goliath::API` instance. A simple server looks like this:
```ruby
require 'goliath'
require './app'
Faye::WebSocket.load_adapter('goliath')
class EchoServer < Goliath::API
def response(env)
App.call(env)
end
end
```
`Faye::WebSocket` can also be used inline within a Goliath app:
```ruby
require 'goliath'
require 'faye/websocket'
Faye::WebSocket.load_adapter('goliath')
class EchoServer < Goliath::API
def response(env)
ws = Faye::WebSocket.new(env)
ws.on :message do |event|
ws.send(event.data)
end
ws.rack_response
end
end
```
faye-websocket-ruby-0.11.0/examples/ 0000775 0000000 0000000 00000000000 13711041666 0017303 5 ustar 00root root 0000000 0000000 faye-websocket-ruby-0.11.0/examples/app.rb 0000664 0000000 0000000 00000002205 13711041666 0020407 0 ustar 00root root 0000000 0000000 require 'faye/websocket'
require 'permessage_deflate'
require 'rack'
static = Rack::File.new(File.dirname(__FILE__))
options = { :extensions => [PermessageDeflate], :ping => 5 }
App = lambda do |env|
if Faye::WebSocket.websocket?(env)
ws = Faye::WebSocket.new(env, ['irc', 'xmpp'], options)
p [:open, ws.url, ws.version, ws.protocol]
ws.onmessage = lambda do |event|
ws.send(event.data)
end
ws.onclose = lambda do |event|
p [:close, event.code, event.reason]
ws = nil
end
ws.rack_response
elsif Faye::EventSource.eventsource?(env)
es = Faye::EventSource.new(env)
time = es.last_event_id.to_i
p [:open, es.url, es.last_event_id]
loop = EM.add_periodic_timer(2) do
time += 1
es.send("Time: #{ time }")
EM.add_timer(1) do
es.send('Update!!', :event => 'update', :id => time) if es
end
end
es.send("Welcome!\n\nThis is an EventSource server.")
es.onclose = lambda do |event|
EM.cancel_timer(loop)
p [:close, es.url]
es = nil
end
es.rack_response
else
static.call(env)
end
end
def App.log(message)
end
faye-websocket-ruby-0.11.0/examples/autobahn_client.rb 0000664 0000000 0000000 00000002413 13711041666 0022767 0 ustar 00root root 0000000 0000000 require 'bundler/setup'
require 'cgi'
require 'faye/websocket'
require 'permessage_deflate'
require 'progressbar'
EM.run {
ruby = RUBY_PLATFORM =~ /java/ ? 'jruby' : 'ruby'
version = defined?(RUBY_ENGINE_VERSION) ? RUBY_ENGINE_VERSION : RUBY_VERSION
version += " (#{ RUBY_VERSION })" if ruby == 'jruby'
host = 'ws://0.0.0.0:9001'
agent = CGI.escape("#{ ruby }-#{ version }")
cases = 0
options = { :extensions => [PermessageDeflate] }
socket = Faye::WebSocket::Client.new("#{ host }/getCaseCount")
progress = nil
socket.onmessage = lambda do |event|
puts "Total cases to run: #{ event.data }"
cases = event.data.to_i
progress = ProgressBar.create(:title => 'Autobahn', :total => cases)
end
run_case = lambda do |n|
if n > cases
socket = Faye::WebSocket::Client.new("#{ host }/updateReports?agent=#{ agent }")
socket.onclose = lambda { |e| EM.stop }
next
end
url = "#{ host }/runCase?case=#{ n }&agent=#{ agent }"
socket = Faye::WebSocket::Client.new(url, [], options)
socket.onmessage = lambda do |event|
socket.send(event.data)
end
socket.on :close do |event|
progress.increment
run_case[n + 1]
end
end
socket.onclose = lambda do |event|
run_case[1]
end
}
faye-websocket-ruby-0.11.0/examples/client.rb 0000664 0000000 0000000 00000001460 13711041666 0021107 0 ustar 00root root 0000000 0000000 require 'bundler/setup'
require 'faye/websocket'
require 'eventmachine'
require 'permessage_deflate'
EM.run {
url = ARGV[0]
proxy = ARGV[1]
ca = File.expand_path('../../spec/server.crt', __FILE__)
ws = Faye::WebSocket::Client.new(url, [],
:proxy => { :origin => proxy, :headers => { 'User-Agent' => 'Echo' } },
:tls => { :root_cert_file => ca },
:headers => { 'Origin' => 'http://faye.jcoglan.com' },
:extensions => [PermessageDeflate]
)
ws.onopen = lambda do |event|
p [:open, ws.headers]
ws.send('mic check')
end
ws.onclose = lambda do |close|
p [:close, close.code, close.reason]
EM.stop
end
ws.onerror = lambda do |error|
p [:error, error.message]
end
ws.onmessage = lambda do |message|
p [:message, message.data]
end
}
faye-websocket-ruby-0.11.0/examples/config.ru 0000664 0000000 0000000 00000000504 13711041666 0021117 0 ustar 00root root 0000000 0000000 # Run using your favourite server:
#
# thin start -R examples/config.ru -p 7000
# rainbows -c examples/rainbows.conf -E production examples/config.ru -p 7000
require 'bundler/setup'
require File.expand_path('../app', __FILE__)
Faye::WebSocket.load_adapter('thin')
Faye::WebSocket.load_adapter('rainbows')
run App
faye-websocket-ruby-0.11.0/examples/haproxy.conf 0000664 0000000 0000000 00000000547 13711041666 0021652 0 ustar 00root root 0000000 0000000 defaults
mode http
timeout client 5s
timeout connect 5s
timeout server 5s
frontend all 0.0.0.0:3000
mode http
timeout client 120s
option forwardfor
option http-server-close
option http-pretend-keepalive
default_backend sockets
backend sockets
balance uri depth 2
timeout server 120s
server socket1 127.0.0.1:7000
faye-websocket-ruby-0.11.0/examples/proxy_server.rb 0000664 0000000 0000000 00000000410 13711041666 0022372 0 ustar 00root root 0000000 0000000 require 'bundler/setup'
require 'eventmachine'
require 'websocket/driver'
require File.expand_path('../../spec/proxy_server', __FILE__)
port = ARGV[0]
secure = ARGV[1] == 'tls'
EM.run {
proxy = ProxyServer.new(:debug => true)
proxy.listen(port, secure)
}
faye-websocket-ruby-0.11.0/examples/rainbows.conf 0000664 0000000 0000000 00000000045 13711041666 0021775 0 ustar 00root root 0000000 0000000 Rainbows! do
use :EventMachine
end
faye-websocket-ruby-0.11.0/examples/server.rb 0000664 0000000 0000000 00000002267 13711041666 0021145 0 ustar 00root root 0000000 0000000 require 'bundler/setup'
port = ARGV[0] || 7000
secure = ARGV[1] == 'tls'
engine = ARGV[2] || 'thin'
spec = File.expand_path('../../spec', __FILE__)
require File.expand_path('../app', __FILE__)
Faye::WebSocket.load_adapter(engine)
case engine
when 'goliath'
class WebSocketServer < Goliath::API
def response(env)
App.call(env)
end
end
when 'puma'
require 'puma/binder'
require 'puma/events'
events = Puma::Events.new($stdout, $stderr)
binder = Puma::Binder.new(events)
binder.parse(["tcp://0.0.0.0:#{ port }"], App)
server = Puma::Server.new(App, events)
server.binder = binder
server.run.join
when 'rainbows'
rackup = Unicorn::Configurator::RACKUP
rackup[:port] = port
rackup[:set_listener] = true
options = rackup[:options]
options[:config_file] = File.expand_path('../rainbows.conf', __FILE__)
Rainbows::HttpServer.new(App, options).start.join
when 'thin'
thin = Rack::Handler.get('thin')
thin.run(App, :Host => '0.0.0.0', :Port => port) do |server|
if secure
server.ssl_options = {
:private_key_file => spec + '/server.key',
:cert_chain_file => spec + '/server.crt'
}
server.ssl = true
end
end
end
faye-websocket-ruby-0.11.0/examples/sse.html 0000664 0000000 0000000 00000001525 13711041666 0020766 0 ustar 00root root 0000000 0000000
EventSource test
EventSource test
faye-websocket-ruby-0.11.0/examples/ws.html 0000664 0000000 0000000 00000002211 13711041666 0020616 0 ustar 00root root 0000000 0000000
WebSocket test
WebSocket test
faye-websocket-ruby-0.11.0/faye-websocket.gemspec 0000664 0000000 0000000 00000002656 13711041666 0021753 0 ustar 00root root 0000000 0000000 Gem::Specification.new do |s|
s.name = 'faye-websocket'
s.version = '0.11.0'
s.summary = 'Standards-compliant WebSocket server and client'
s.author = 'James Coglan'
s.email = 'jcoglan@gmail.com'
s.homepage = 'https://github.com/faye/faye-websocket-ruby'
s.license = 'Apache-2.0'
s.extra_rdoc_files = %w[README.md]
s.rdoc_options = %w[--main README.md --markup markdown]
s.require_paths = %w[lib]
s.files = %w[CHANGELOG.md LICENSE.md README.md] + Dir.glob('lib/**/*.rb')
s.add_dependency 'eventmachine', '>= 0.12.0'
s.add_dependency 'websocket-driver', '>= 0.5.1'
s.add_development_dependency 'permessage_deflate'
s.add_development_dependency 'progressbar'
s.add_development_dependency 'puma', '>= 2.0.0'
s.add_development_dependency 'rack'
s.add_development_dependency 'rspec'
s.add_development_dependency 'rspec-eventmachine', '>= 0.2.0'
jruby = RUBY_PLATFORM =~ /java/
rbx = defined?(RUBY_ENGINE) && RUBY_ENGINE =~ /rbx/
if RUBY_VERSION < '2.0.0'
s.add_development_dependency 'public_suffix', '< 1.5.0'
end
unless jruby
s.add_development_dependency 'rainbows', '~> 4.4.0'
s.add_development_dependency 'thin', '>= 1.2.0'
end
unless rbx
goliath_version = (RUBY_VERSION < '2.1.0') ? '<= 1.0.4' : '> 0'
s.add_development_dependency 'goliath', goliath_version
end
unless jruby or rbx
s.add_development_dependency 'passenger', '>= 4.0.0'
end
end
faye-websocket-ruby-0.11.0/lib/ 0000775 0000000 0000000 00000000000 13711041666 0016233 5 ustar 00root root 0000000 0000000 faye-websocket-ruby-0.11.0/lib/faye/ 0000775 0000000 0000000 00000000000 13711041666 0017157 5 ustar 00root root 0000000 0000000 faye-websocket-ruby-0.11.0/lib/faye/adapters/ 0000775 0000000 0000000 00000000000 13711041666 0020762 5 ustar 00root root 0000000 0000000 faye-websocket-ruby-0.11.0/lib/faye/adapters/goliath.rb 0000664 0000000 0000000 00000001730 13711041666 0022737 0 ustar 00root root 0000000 0000000 class Goliath::Connection
attr_accessor :socket_stream
alias :goliath_receive_data :receive_data
def receive_data(data)
if @serving == :websocket
socket_stream.receive(data) if socket_stream
else
goliath_receive_data(data)
socket_stream.receive(@parser.upgrade_data) if socket_stream
@serving = :websocket if @api.websocket?
end
end
def unbind
super
ensure
socket_stream.fail if socket_stream
end
end
class Goliath::API
include Faye::WebSocket::Adapter
alias :goliath_call :call
def call(env)
@env = env
goliath_call(env)
end
end
class Goliath::Request
alias :goliath_process :process
def process
env['em.connection'] = conn
goliath_process
end
end
class Goliath::Response
alias :goliath_head :head
alias :goliath_headers_output :headers_output
def head
(status == 101) ? '' : goliath_head
end
def headers_output
(status == 101) ? '' : goliath_headers_output
end
end
faye-websocket-ruby-0.11.0/lib/faye/adapters/rainbows.rb 0000664 0000000 0000000 00000002555 13711041666 0023142 0 ustar 00root root 0000000 0000000 # WebSocket extensions for Rainbows
# Based on code from the Cramp project
# http://github.com/lifo/cramp
# Copyright (c) 2009-2011 Pratik Naik
#
# 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.
module Faye
class WebSocket
autoload :RainbowsClient, File.expand_path('../rainbows_client', __FILE__)
end
end
Rainbows::O[:em_client_class] = "Faye::WebSocket::RainbowsClient"
faye-websocket-ruby-0.11.0/lib/faye/adapters/rainbows_client.rb 0000664 0000000 0000000 00000004221 13711041666 0024470 0 ustar 00root root 0000000 0000000 # WebSocket extensions for Rainbows
# Based on code from the Cramp project
# http://github.com/lifo/cramp
# Copyright (c) 2009-2011 Pratik Naik
#
# 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.
module Faye
class WebSocket
class RainbowsClient < Rainbows::EventMachine::Client
include Faye::WebSocket::Adapter
attr_accessor :socket_stream
def receive_data(data)
return super unless @state == :websocket
socket_stream.receive(data) if socket_stream
end
def app_call(*args)
@env['em.connection'] = self
if args.first == NULL_IO and @hp.content_length == 0 and websocket?
prepare_request_body
else
super
end
end
def on_read(data)
if @state == :body and websocket? and @hp.body_eof?
@state = :websocket
@input.rewind
app_call StringIO.new(@buf)
else
super
end
end
def unbind
super
ensure
socket_stream.fail if socket_stream
end
def write_headers(status, headers, *args)
super unless socket_connection? and status == 101
end
end
end
end
faye-websocket-ruby-0.11.0/lib/faye/adapters/thin.rb 0000664 0000000 0000000 00000003721 13711041666 0022254 0 ustar 00root root 0000000 0000000 # WebSocket extensions for Thin
# Based on code from the Cramp project
# http://github.com/lifo/cramp
# Copyright (c) 2009-2011 Pratik Naik
#
# 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.
class Thin::Connection
attr_accessor :socket_stream
alias :thin_process :process
alias :thin_receive_data :receive_data
def process
@serving ||= nil
if @serving != :websocket and @request.websocket?
@serving = :websocket
end
if @request.socket_connection?
@request.env['em.connection'] = self
@response.persistent!
@response.async = true
end
thin_process
end
def receive_data(data)
@serving ||= nil
return thin_receive_data(data) unless @serving == :websocket
socket_stream.receive(data) if socket_stream
end
end
class Thin::Request
include Faye::WebSocket::Adapter
end
class Thin::Response
attr_accessor :async
alias :thin_head :head
def head
return '' if async and status == 101
thin_head
end
end
faye-websocket-ruby-0.11.0/lib/faye/eventsource.rb 0000664 0000000 0000000 00000005716 13711041666 0022057 0 ustar 00root root 0000000 0000000 require File.expand_path('../websocket', __FILE__) unless defined?(Faye::WebSocket)
module Faye
class EventSource
include WebSocket::API::EventTarget
attr_reader :env, :url, :ready_state
DEFAULT_RETRY = 5
def self.eventsource?(env)
return false unless env['REQUEST_METHOD'] == 'GET'
accept = (env['HTTP_ACCEPT'] || '').split(/\s*,\s*/)
accept.include?('text/event-stream')
end
def self.determine_url(env)
WebSocket.determine_url(env, ['https', 'http'])
end
def initialize(env, options = {})
WebSocket.ensure_reactor_running
super()
@env = env
@ping = options[:ping]
@retry = (options[:retry] || DEFAULT_RETRY).to_f
@url = EventSource.determine_url(env)
@stream = Stream.new(self)
@ready_state = WebSocket::API::CONNECTING
headers = ::WebSocket::Driver::Headers.new
if options[:headers]
options[:headers].each { |k,v| headers[k] = v }
end
if callback = @env['async.callback']
callback.call([101, {}, @stream])
end
@stream.write("HTTP/1.1 200 OK\r\n" +
"Content-Type: text/event-stream\r\n" +
"Cache-Control: no-cache, no-store\r\n" +
"Connection: close\r\n" +
headers.to_s +
"\r\n" +
"retry: #{ (@retry * 1000).floor }\r\n\r\n")
EventMachine.next_tick { open }
if @ping
@ping_timer = EventMachine.add_periodic_timer(@ping) { ping }
end
end
def last_event_id
@env['HTTP_LAST_EVENT_ID'] || ''
end
def rack_response
[ -1, {}, [] ]
end
private
def open
return unless @ready_state == WebSocket::API::CONNECTING
@ready_state = WebSocket::API::OPEN
event = WebSocket::API::Event.create('open')
event.init_event('open', false, false)
dispatch_event(event)
end
public
def send(message, options = {})
return false if @ready_state > WebSocket::API::OPEN
message = ::WebSocket::Driver.encode(message.to_s).
gsub(/(\r\n|\r|\n)/, '\1data: ')
frame = ""
frame << "event: #{ options[:event] }\r\n" if options[:event]
frame << "id: #{ options[:id] }\r\n" if options[:id]
frame << "data: #{ message }\r\n\r\n"
@stream.write(frame)
true
end
def ping(message = nil)
return false if @ready_state > WebSocket::API::OPEN
@stream.write(":\r\n\r\n")
true
end
def close
return if [WebSocket::API::CLOSING, WebSocket::API::CLOSED].include?(@ready_state)
@ready_state = WebSocket::API::CLOSED
EventMachine.cancel_timer(@ping_timer)
@stream.close_connection_after_writing
event = WebSocket::API::Event.create('close')
event.init_event('close', false, false)
dispatch_event(event)
end
class Stream < RackStream
def fail
@socket_object.close
end
end
end
end
faye-websocket-ruby-0.11.0/lib/faye/rack_stream.rb 0000664 0000000 0000000 00000004000 13711041666 0021771 0 ustar 00root root 0000000 0000000 module Faye
class RackStream
include EventMachine::Deferrable
module Reader
attr_accessor :stream
def receive_data(data)
stream.receive(data)
end
def unbind
stream.fail
end
end
def initialize(socket)
@socket_object = socket
@connection = socket.env['em.connection']
@stream_send = socket.env['stream.send']
@rack_hijack_io = @rack_hijack_io_reader = nil
hijack_rack_socket
@connection.socket_stream = self if @connection.respond_to?(:socket_stream)
end
def hijack_rack_socket
return unless @socket_object.env['rack.hijack']
@socket_object.env['rack.hijack'].call
@rack_hijack_io = @socket_object.env['rack.hijack_io']
queue = Queue.new
EventMachine.schedule do
begin
EventMachine.attach(@rack_hijack_io, Reader) do |reader|
reader.stream = self
if @rack_hijack_io
@rack_hijack_io_reader = reader
else
reader.close_connection_after_writing
end
end
ensure
queue.push(nil)
end
end
queue.pop if EventMachine.reactor_running?
end
def clean_rack_hijack
return unless @rack_hijack_io
@rack_hijack_io_reader.close_connection_after_writing
@rack_hijack_io = @rack_hijack_io_reader = nil
end
def close_connection
clean_rack_hijack
@connection.close_connection if @connection
end
def close_connection_after_writing
clean_rack_hijack
@connection.close_connection_after_writing if @connection
end
def each(&callback)
@stream_send ||= callback
end
def fail
end
def receive(data)
end
def write(data)
return @rack_hijack_io_reader.send_data(data) if @rack_hijack_io_reader
return @rack_hijack_io.write(data) if @rack_hijack_io
return @stream_send.call(data) if @stream_send
rescue => e
fail if EOFError === e
end
end
end
faye-websocket-ruby-0.11.0/lib/faye/websocket.rb 0000664 0000000 0000000 00000005463 13711041666 0021502 0 ustar 00root root 0000000 0000000 # API references:
#
# * https://html.spec.whatwg.org/multipage/comms.html#network
# * https://dom.spec.whatwg.org/#interface-eventtarget
# * https://dom.spec.whatwg.org/#interface-event
require 'forwardable'
require 'stringio'
require 'uri'
require 'eventmachine'
require 'websocket/driver'
module Faye
autoload :EventSource, File.expand_path('../eventsource', __FILE__)
autoload :RackStream, File.expand_path('../rack_stream', __FILE__)
class WebSocket
root = File.expand_path('../websocket', __FILE__)
autoload :Adapter, root + '/adapter'
autoload :API, root + '/api'
autoload :Client, root + '/client'
autoload :SslVerifier, root + '/ssl_verifier'
ADAPTERS = {
'goliath' => :Goliath,
'rainbows' => :Rainbows,
'thin' => :Thin
}
def self.determine_url(env, schemes = ['wss', 'ws'])
scheme = schemes[secure_request?(env) ? 0 : 1]
host = env['HTTP_HOST']
path = env['PATH_INFO']
query = env['QUERY_STRING'].to_s
scheme + '://' + host + path + (query.empty? ? '' : '?' + query)
end
def self.ensure_reactor_running
Thread.new { EventMachine.run } unless EventMachine.reactor_running?
Thread.pass until EventMachine.reactor_running?
end
def self.load_adapter(backend)
const = Kernel.const_get(ADAPTERS[backend]) rescue nil
require(backend) unless const
path = File.expand_path("../adapters/#{ backend }.rb", __FILE__)
require(path) if File.file?(path)
end
def self.secure_request?(env)
return true if env['HTTPS'] == 'on'
return true if env['HTTP_X_FORWARDED_SSL'] == 'on'
return true if env['HTTP_X_FORWARDED_SCHEME'] == 'https'
return true if env['HTTP_X_FORWARDED_PROTO'] == 'https'
return true if env['rack.url_scheme'] == 'https'
return false
end
def self.websocket?(env)
::WebSocket::Driver.websocket?(env)
end
attr_reader :env
include API
def initialize(env, protocols = nil, options = {})
WebSocket.ensure_reactor_running
@env = env
@url = WebSocket.determine_url(@env)
super(options) { ::WebSocket::Driver.rack(self, :max_length => options[:max_length], :protocols => protocols) }
@driver_started = false
@stream = Stream.new(self)
if callback = @env['async.callback']
callback.call([101, {}, @stream])
end
end
def start_driver
return if @driver.nil? || @driver_started
@driver_started = true
EventMachine.schedule { @driver.start }
end
def rack_response
start_driver
[ -1, {}, [] ]
end
class Stream < RackStream
def fail
@socket_object.__send__(:finalize_close)
end
def receive(data)
@socket_object.__send__(:parse, data)
end
end
end
end
faye-websocket-ruby-0.11.0/lib/faye/websocket/ 0000775 0000000 0000000 00000000000 13711041666 0021145 5 ustar 00root root 0000000 0000000 faye-websocket-ruby-0.11.0/lib/faye/websocket/adapter.rb 0000664 0000000 0000000 00000000557 13711041666 0023121 0 ustar 00root root 0000000 0000000 module Faye
class WebSocket
module Adapter
def websocket?
e = defined?(@env) ? @env : env
e && WebSocket.websocket?(e)
end
def eventsource?
e = defined?(@env) ? @env : env
e && EventSource.eventsource?(e)
end
def socket_connection?
websocket? or eventsource?
end
end
end
end
faye-websocket-ruby-0.11.0/lib/faye/websocket/api.rb 0000664 0000000 0000000 00000010435 13711041666 0022246 0 ustar 00root root 0000000 0000000 require File.expand_path('../api/event_target', __FILE__)
require File.expand_path('../api/event', __FILE__)
module Faye
class WebSocket
module API
CONNECTING = 0
OPEN = 1
CLOSING = 2
CLOSED = 3
CLOSE_TIMEOUT = 30
include EventTarget
extend Forwardable
def_delegators :@driver, :version
attr_reader :url, :ready_state, :buffered_amount
def initialize(options = {})
@ready_state = CONNECTING
super()
::WebSocket::Driver.validate_options(options, [:headers, :extensions, :max_length, :ping, :proxy, :tls])
@driver = yield
if headers = options[:headers]
headers.each { |name, value| @driver.set_header(name, value) }
end
[*options[:extensions]].each do |extension|
@driver.add_extension(extension)
end
@ping = options[:ping]
@ping_id = 0
@buffered_amount = 0
@close_params = @close_timer = @ping_timer = @proxy = @stream = nil
@onopen = @onmessage = @onclose = @onerror = nil
@driver.on(:open) { |e| open }
@driver.on(:message) { |e| receive_message(e.data) }
@driver.on(:close) { |e| begin_close(e.reason, e.code, :wait_for_write => true) }
@driver.on(:error) do |error|
emit_error(error.message)
end
if @ping
@ping_timer = EventMachine.add_periodic_timer(@ping) do
@ping_id += 1
ping(@ping_id.to_s)
end
end
end
def write(data)
@stream.write(data)
end
def send(message)
return false if @ready_state > OPEN
case message
when Numeric then @driver.text(message.to_s)
when String then @driver.text(message)
when Array then @driver.binary(message)
else false
end
end
def ping(message = '', &callback)
return false if @ready_state > OPEN
@driver.ping(message, &callback)
end
def close(code = nil, reason = nil)
code ||= 1000
reason ||= ''
unless code == 1000 or (code >= 3000 and code <= 4999)
raise ArgumentError, "Failed to execute 'close' on WebSocket: " +
"The code must be either 1000, or between 3000 and 4999. " +
"#{ code } is neither."
end
@ready_state = CLOSING unless @ready_state == CLOSED
@close_timer = EventMachine.add_timer(CLOSE_TIMEOUT) { begin_close('', 1006) }
@driver.close(reason, code)
end
def protocol
@driver.protocol || ''
end
private
def open
return unless @ready_state == CONNECTING
@ready_state = OPEN
event = Event.create('open')
event.init_event('open', false, false)
dispatch_event(event)
end
def receive_message(data)
return unless @ready_state == OPEN
event = Event.create('message', :data => data)
event.init_event('message', false, false)
dispatch_event(event)
end
def emit_error(message)
return if @ready_state >= CLOSING
event = Event.create('error', :message => message)
event.init_event('error', false, false)
dispatch_event(event)
end
def begin_close(reason, code, options = {})
return if @ready_state == CLOSED
@ready_state = CLOSING
@close_params = [reason, code]
if @stream
if options[:wait_for_write]
@stream.close_connection_after_writing
else
@stream.close_connection
end
else
finalize_close
end
end
def finalize_close
return if @ready_state == CLOSED
@ready_state = CLOSED
EventMachine.cancel_timer(@close_timer) if @close_timer
EventMachine.cancel_timer(@ping_timer) if @ping_timer
reason = @close_params ? @close_params[0] : ''
code = @close_params ? @close_params[1] : 1006
event = Event.create('close', :code => code, :reason => reason)
event.init_event('close', false, false)
dispatch_event(event)
end
def parse(data)
worker = @proxy || @driver
worker.parse(data)
end
end
end
end
faye-websocket-ruby-0.11.0/lib/faye/websocket/api/ 0000775 0000000 0000000 00000000000 13711041666 0021716 5 ustar 00root root 0000000 0000000 faye-websocket-ruby-0.11.0/lib/faye/websocket/api/event.rb 0000664 0000000 0000000 00000002014 13711041666 0023361 0 ustar 00root root 0000000 0000000 module Faye::WebSocket::API
class Event
attr_reader :type, :bubbles, :cancelable
attr_accessor :target, :current_target, :event_phase
CAPTURING_PHASE = 1
AT_TARGET = 2
BUBBLING_PHASE = 3
def initialize(event_type, options)
@type = event_type
options.each { |key, value| instance_variable_set("@#{ key }", value) }
end
def init_event(event_type, can_bubble, cancelable)
@type = event_type
@bubbles = can_bubble
@cancelable = cancelable
end
def stop_propagation
end
def prevent_default
end
end
class OpenEvent < Event
end
class MessageEvent < Event
attr_reader :data
end
class CloseEvent < Event
attr_reader :code, :reason
end
class ErrorEvent < Event
attr_reader :message
end
TYPES = {
'open' => OpenEvent,
'message' => MessageEvent,
'close' => CloseEvent,
'error' => ErrorEvent
}
def Event.create(type, options = {})
TYPES[type].new(type, options)
end
end
faye-websocket-ruby-0.11.0/lib/faye/websocket/api/event_target.rb 0000664 0000000 0000000 00000002763 13711041666 0024742 0 ustar 00root root 0000000 0000000 module Faye::WebSocket::API
module EventTarget
include ::WebSocket::Driver::EventEmitter
events = %w[open message error close]
events.each do |event_type|
define_method "on#{ event_type }=" do |handler|
EventMachine.next_tick do
flush(event_type, handler)
instance_variable_set("@on#{ event_type }", handler)
end
end
end
def add_event_listener(event_type, listener, use_capture = false)
add_listener(event_type, &listener)
end
def add_listener(event_type, callable = nil, &block)
listener = callable || block
EventMachine.next_tick do
flush(event_type, listener)
super(event_type, &listener)
end
end
def remove_event_listener(event_type, listener, use_capture = false)
remove_listener(event_type, &listener)
end
def dispatch_event(event)
event.target = event.current_target = self
event.event_phase = Event::AT_TARGET
listener = instance_variable_get("@on#{ event.type }")
count = listener_count(event.type)
unless listener or count > 0
event_buffers[event.type].push(event)
end
listener.call(event) if listener
emit(event.type, event)
end
private
def flush(event_type, listener)
if buffer = event_buffers.delete(event_type.to_s)
buffer.each { |event| listener.call(event) }
end
end
def event_buffers
@event_buffers ||= Hash.new { |k,v| k[v] = [] }
end
end
end
faye-websocket-ruby-0.11.0/lib/faye/websocket/client.rb 0000664 0000000 0000000 00000005722 13711041666 0022756 0 ustar 00root root 0000000 0000000 require 'forwardable'
module Faye
class WebSocket
class Client
extend Forwardable
include API
DEFAULT_PORTS = { 'http' => 80, 'https' => 443, 'ws' => 80, 'wss' => 443 }
SECURE_PROTOCOLS = ['https', 'wss']
def_delegators :@driver, :headers, :status
def initialize(url, protocols = nil, options = {})
@url = url
super(options) { ::WebSocket::Driver.client(self, :max_length => options[:max_length], :protocols => protocols) }
proxy = options.fetch(:proxy, {})
@endpoint = URI.parse(proxy[:origin] || @url)
port = @endpoint.port || DEFAULT_PORTS[@endpoint.scheme]
@origin_tls = options.fetch(:tls, {})
@socket_tls = proxy[:origin] ? proxy.fetch(:tls, {}) : @origin_tls
configure_proxy(proxy)
EventMachine.connect(@endpoint.host, port, Connection) do |conn|
conn.parent = self
end
rescue => error
on_network_error(error)
end
private
def configure_proxy(proxy)
return unless proxy[:origin]
@proxy = @driver.proxy(proxy[:origin])
@proxy.on(:error) { |error| @driver.emit(:error, error) }
if headers = proxy[:headers]
headers.each { |name, value| @proxy.set_header(name, value) }
end
@proxy.on(:connect) do
@proxy = nil
start_tls(URI.parse(@url), @origin_tls)
@driver.start
end
end
def start_tls(uri, options)
return unless SECURE_PROTOCOLS.include?(uri.scheme)
tls_options = { :sni_hostname => uri.host, :verify_peer => true }.merge(options)
@ssl_verifier = SslVerifier.new(uri.host, tls_options)
@stream.start_tls(tls_options)
end
def on_connect(stream)
@stream = stream
start_tls(@endpoint, @socket_tls)
worker = @proxy || @driver
worker.start
end
def on_network_error(error)
emit_error("Network error: #{ @url }: #{ error.message }")
finalize_close
end
def ssl_verify_peer(cert)
@ssl_verifier.ssl_verify_peer(cert)
rescue => error
on_network_error(error)
end
def ssl_handshake_completed
@ssl_verifier.ssl_handshake_completed
rescue => error
on_network_error(error)
end
module Connection
attr_accessor :parent
def connection_completed
parent.__send__(:on_connect, self)
end
def ssl_verify_peer(cert)
parent.__send__(:ssl_verify_peer, cert)
end
def ssl_handshake_completed
parent.__send__(:ssl_handshake_completed)
end
def receive_data(data)
parent.__send__(:parse, data)
end
def unbind(error = nil)
parent.__send__(:emit_error, error) if error
parent.__send__(:finalize_close)
end
def write(data)
send_data(data) rescue nil
end
end
end
end
end
faye-websocket-ruby-0.11.0/lib/faye/websocket/ssl_verifier.rb 0000664 0000000 0000000 00000005053 13711041666 0024171 0 ustar 00root root 0000000 0000000 # This code is based on the implementation in Faraday:
#
# https://github.com/lostisland/faraday/blob/v1.0.1/lib/faraday/adapter/em_http_ssl_patch.rb
#
# Faraday is published under the MIT license as detailed here:
#
# https://github.com/lostisland/faraday/blob/v1.0.1/LICENSE.md
#
# Copyright (c) 2009-2019 Rick Olson, Zack Hobson
#
# 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.
require 'openssl'
module Faye
class WebSocket
SSLError = Class.new(OpenSSL::SSL::SSLError)
class SslVerifier
def initialize(hostname, ssl_opts)
@hostname = hostname
@ssl_opts = ssl_opts
@cert_store = OpenSSL::X509::Store.new
if root = @ssl_opts[:root_cert_file]
[root].flatten.each { |ca_path| @cert_store.add_file(ca_path) }
else
@cert_store.set_default_paths
end
end
def ssl_verify_peer(cert_text)
return true unless should_verify?
certificate = parse_cert(cert_text)
return false unless certificate
unless @cert_store.verify(certificate)
raise SSLError, "Unable to verify the server certificate for '#{ @hostname }'"
end
store_cert(certificate)
@last_cert = certificate
true
end
def ssl_handshake_completed
return unless should_verify?
unless identity_verified?
raise SSLError, "Host '#{ @hostname }' does not match the server certificate"
end
end
private
def should_verify?
@ssl_opts[:verify_peer] != false
end
def parse_cert(cert_text)
OpenSSL::X509::Certificate.new(cert_text)
rescue OpenSSL::X509::CertificateError
nil
end
def store_cert(certificate)
@cert_store.add_cert(certificate)
rescue OpenSSL::X509::StoreError => error
raise error unless error.message == 'cert already in hash table'
end
def identity_verified?
@last_cert and OpenSSL::SSL.verify_certificate_identity(@last_cert, @hostname)
end
end
end
end
faye-websocket-ruby-0.11.0/spec/ 0000775 0000000 0000000 00000000000 13711041666 0016417 5 ustar 00root root 0000000 0000000 faye-websocket-ruby-0.11.0/spec/echo_server.rb 0000664 0000000 0000000 00000002413 13711041666 0021250 0 ustar 00root root 0000000 0000000 require 'puma'
require 'puma/binder'
require 'puma/events'
unless RUBY_PLATFORM =~ /java/
Faye::WebSocket.load_adapter('thin')
Thin::Logging.silent = true
end
class EchoServer
def call(env)
socket = Faye::WebSocket.new(env, ["echo"])
socket.onmessage = lambda do |event|
socket.send(event.data)
end
socket.rack_response
end
def log(*args)
end
def listen(port, backend, tls = false)
case backend
when :puma then listen_puma(port, tls)
when :thin then listen_thin(port, tls)
end
end
def stop
case @server
when Puma::Server then @server.stop(true)
else @server.stop
end
end
private
def listen_puma(port, tls)
events = Puma::Events.new(StringIO.new, StringIO.new)
binder = Puma::Binder.new(events)
binder.parse(["tcp://0.0.0.0:#{ port }"], self)
@server = Puma::Server.new(self, events)
@server.binder = binder
@server.run
end
def listen_thin(port, tls)
Rack::Handler.get('thin').run(self, :Port => port) do |s|
if tls
s.ssl = true
s.ssl_options = {
:private_key_file => File.expand_path('../server.key', __FILE__),
:cert_chain_file => File.expand_path('../server.crt', __FILE__)
}
end
@server = s
end
end
end
faye-websocket-ruby-0.11.0/spec/faye/ 0000775 0000000 0000000 00000000000 13711041666 0017343 5 ustar 00root root 0000000 0000000 faye-websocket-ruby-0.11.0/spec/faye/websocket/ 0000775 0000000 0000000 00000000000 13711041666 0021331 5 ustar 00root root 0000000 0000000 faye-websocket-ruby-0.11.0/spec/faye/websocket/client_spec.rb 0000664 0000000 0000000 00000013164 13711041666 0024153 0 ustar 00root root 0000000 0000000 # encoding=utf-8
require "spec_helper"
require "socket"
IS_JRUBY = (defined?(RUBY_ENGINE) && RUBY_ENGINE == 'jruby')
WebSocketSteps = RSpec::EM.async_steps do
def server(port, backend, secure, &callback)
@echo_server = EchoServer.new
@echo_server.listen(port, backend, secure)
EM.add_timer(0.1, &callback)
end
def stop(&callback)
@echo_server.stop
EM.next_tick(&callback)
end
def proxy(port, &callback)
@proxy_server = ProxyServer.new
@proxy_server.listen(port)
EM.add_timer(0.1, &callback)
end
def stop_proxy(&callback)
@proxy_server.stop
EM.next_tick(&callback)
end
def open_socket(url, protocols, &callback)
done = false
resume = lambda do |open|
unless done
done = true
@open = open
callback.call
end
end
@ws = Faye::WebSocket::Client.new(url, protocols, :proxy => { :origin => proxy_url }, :tls => tls_options)
@ws.on(:open) { |e| resume.call(true) }
@ws.onclose = lambda { |e| resume.call(false) }
end
def open_socket_and_close_it_fast(url, protocols, &callback)
@ws = Faye::WebSocket::Client.new(url, protocols, :tls => tls_options)
@ws.on(:open) { |e| @open = @ever_opened = true }
@ws.onclose = lambda { |e| @open = false }
@ws.close
callback.call
end
def close_socket(&callback)
@ws.onclose = lambda do |e|
@open = false
callback.call
end
@ws.close
end
def check_open(status, headers, &callback)
expect(@open).to be(true)
expect(@ws.status).to eq(status)
headers.each do |name, value|
expect(@ws.headers[name]).to eq(value)
end
callback.call
end
def check_closed(&callback)
expect(@open).to be(false)
callback.call
end
def check_never_opened(&callback)
expect(!!@ever_opened).to be(false)
callback.call
end
def check_protocol(protocol, &callback)
expect(@ws.protocol).to eq(protocol)
callback.call
end
def listen_for_message(&callback)
@ws.add_event_listener('message', lambda { |e| @message = e.data })
start = Time.now
timer = EM.add_periodic_timer 0.1 do
if @message or Time.now.to_i - start.to_i > 5
EM.cancel_timer(timer)
callback.call
end
end
end
def send_message(message, &callback)
EM.add_timer(0.5) { @ws.send(message) }
EM.next_tick(&callback)
end
def check_response(message, &callback)
expect(@message).to eq(message)
callback.call
end
def check_no_response(&callback)
expect(@message).to eq(nil)
callback.call
end
def wait(seconds, &callback)
EM.add_timer(seconds, &callback)
end
end
describe Faye::WebSocket::Client do
include WebSocketSteps
let(:protocols) { ["foo", "echo"] }
let(:localhost) { "localhost" }
let(:port) { 4180 }
let(:plain_text_url) { "ws://#{ localhost }:#{ port }/" }
let(:wrong_url) { "ws://#{ localhost }:9999/" }
let(:secure_url) { "wss://#{ localhost }:#{ port }/" }
let :tls_options do
{ :root_cert_file => File.expand_path('../../../server.crt', __FILE__) }
end
shared_examples_for "socket client" do
before do
@ever_opened = @message = nil
end
it "can open a connection" do
open_socket(socket_url, protocols)
check_open(101, { "Upgrade" => "websocket" })
check_protocol("echo")
end
it "cannot open a connection to the wrong host" do
open_socket(blocked_url, protocols)
check_closed
end
it "can close the connection" do
open_socket(socket_url, protocols)
close_socket
check_closed
end
describe "in the OPEN state" do
before { open_socket(socket_url, protocols) }
it "can send and receive messages" do
send_message "I expect this to be echoed"
listen_for_message
check_response "I expect this to be echoed"
end
it "sends numbers as strings" do
send_message 13
listen_for_message
check_response "13"
end
end
describe "in the CLOSED state" do
before do
open_socket(socket_url, protocols)
close_socket
end
it "cannot send and receive messages" do
send_message "I expect this to be echoed"
listen_for_message
check_no_response
end
end
it "can be closed before connecting" do
open_socket_and_close_it_fast(socket_url, protocols)
wait 0.01
check_closed
check_never_opened
end
end
shared_examples_for "socket server" do
describe "with a Puma server" do
let(:localhost) { "0.0.0.0" }
let(:socket_url) { plain_text_url }
let(:blocked_url) { wrong_url }
before { server port, :puma, false }
after { stop }
it_should_behave_like "socket client"
end
describe "with a plain-text Thin server" do
next if IS_JRUBY
let(:socket_url) { plain_text_url }
let(:blocked_url) { secure_url }
before { server port, :thin, false }
after { stop }
it_should_behave_like "socket client"
end
describe "with a secure Thin server" do
next if IS_JRUBY
let(:socket_url) { secure_url }
let(:blocked_url) { plain_text_url }
before { server port, :thin, true }
after { stop }
it_should_behave_like "socket client"
end
end
describe "with no proxy" do
let(:proxy_url) { nil }
it_should_behave_like "socket server"
end
describe "with a proxy" do
let(:proxy_port) { 4181 }
let(:proxy_url) { "http://localhost:#{ proxy_port }" }
next if IS_JRUBY
before { proxy proxy_port }
after { stop_proxy }
it_should_behave_like "socket server"
end
end
faye-websocket-ruby-0.11.0/spec/proxy_server.rb 0000664 0000000 0000000 00000003507 13711041666 0021520 0 ustar 00root root 0000000 0000000 class ProxyServer
def initialize(options = {})
@options = options
end
def listen(port, tls = false)
@server = EM.start_server('localhost', port, Frontend) do |frontend|
if tls
frontend.start_tls(
:private_key_file => File.expand_path('../server.key', __FILE__),
:cert_chain_file => File.expand_path('../server.crt', __FILE__)
)
end
frontend.debug = @options[:debug]
end
end
def stop
EM.stop_server(@server) if @server
end
def self.format(data)
data.bytes.map { |b| "%02x" % b }.join(' ')
end
module Frontend
attr_writer :debug
def post_init
@request = WebSocket::HTTP::Request.new
@backend = nil
end
def receive_data(data)
if @backend
p [:I, ProxyServer.format(data)] if @debug
return @backend.send_data(data)
end
@request.parse(data)
return unless @request.complete?
unless @request.env['REQUEST_METHOD'] == 'CONNECT'
send_data("HTTP/1.1 403 Forbidden\r\n\r\n")
return close_connection_after_writing
end
p @request.env if @debug
hostname, port = @request.env['PATH_INFO'].split(':', 2)
EM.connect(hostname, port, Backend) do |backend|
backend.debug = @debug
backend.frontend = self
end
end
def unbind
@backend.close_connection_after_writing if @backend
end
def return_handshake(backend)
@backend = backend
send_data("HTTP/1.1 200 OK\r\n\r\n")
end
end
module Backend
attr_writer :debug, :frontend
def connection_completed
@frontend.return_handshake(self)
end
def receive_data(data)
p [:O, ProxyServer.format(data)] if @debug
@frontend.send_data(data)
end
def unbind
@frontend.close_connection_after_writing
end
end
end
faye-websocket-ruby-0.11.0/spec/server.crt 0000664 0000000 0000000 00000001157 13711041666 0020443 0 ustar 00root root 0000000 0000000 -----BEGIN CERTIFICATE-----
MIIBnzCCAQgCCQCPSxx7kzVcOjANBgkqhkiG9w0BAQUFADAUMRIwEAYDVQQDDAls
b2NhbGhvc3QwHhcNMjAwNTE0MTYzNzA4WhcNMjEwNTE0MTYzNzA4WjAUMRIwEAYD
VQQDDAlsb2NhbGhvc3QwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAMrHfNcE
oqn4Na4TnpoxvIwDu7aBV/qRDPit+i7wZpMKBcUJVNQXfLsB4lJEgJwJ9QnPl6TX
Wsi9lbcpDsEdtVhmuKFBlS+/sgVh/MR6H/8VdxPq/uq/t/VXYbQitMveHdTtprve
bDSTB5+JAD8j5GSdML0A5mov5UXMdPqZMos5AgMBAAEwDQYJKoZIhvcNAQEFBQAD
gYEABwcVvH5AslMUbKKjF4baWHO2dDji+6bc9C7jEdtJ6kDlXW7NDlyvra31NTgB
tySfNK5VEd4hcv3X2DJvYboTjd0PB1OCv9vJvGus9aiKAAtM4YjYWDvfBtqwjxtM
88aTZWUI7JEs1zurcVGgm9cne3lmdGQpHDnh09WPlxKGkyc=
-----END CERTIFICATE-----
faye-websocket-ruby-0.11.0/spec/server.key 0000664 0000000 0000000 00000001567 13711041666 0020450 0 ustar 00root root 0000000 0000000 -----BEGIN RSA PRIVATE KEY-----
MIICXAIBAAKBgQDKx3zXBKKp+DWuE56aMbyMA7u2gVf6kQz4rfou8GaTCgXFCVTU
F3y7AeJSRICcCfUJz5ek11rIvZW3KQ7BHbVYZrihQZUvv7IFYfzEeh//FXcT6v7q
v7f1V2G0IrTL3h3U7aa73mw0kwefiQA/I+RknTC9AOZqL+VFzHT6mTKLOQIDAQAB
AoGAd3hY4QXmIGYQmmMMyqQLBiMc8UX7Y7EaB1nZUNZqWK9K/u+kfuDnp/ZQNVAO
xeBH8mPQYFpRLnwkPSl/e9/Y+xO94w6VE2SCluSk+V5ahpK1j6lwCKeC3bGHQaun
oiqjSAK5XukA/v213x7B6JIY8rJozm+DcBZ6HP10Q6Ge1hECQQDswcxbPHl3HrEW
4hORV4m1zdOy9D3W31G1vBvIRdBV+npSiBHfaHI8B2nRNHZ7XnnT8jH00SgAuw9P
RyXo3HIvAkEA20K2y9NNVRR12LkpqRUELVMmgDtaTU7ij7Z+bHVqNlcNBVnLGq0J
tuRwCCVEggFxddCccT7Rg3VLk1Cr1csHFwJBAOya9LaNJjqEYqgiwTl0CTy2eQWT
jyVowojjd8Ra+Myj6JEISQxCnj8djYO0cV/zV78XgRNP3zFzv+YTjXA8tqMCQEwp
n2+r5YfxRIJUx2jpWPsZkB0pANQr271KCh5Ipu1bTbWXHFsW0nIKZfQeZe6265+e
KEmaJZMBcpAojJgGH18CQDbHZxziKnSqXIb/U8E8KonWh52j9OSFLzSVawFeKuhA
MgeEZWl8lyt0sNbzZnxHlME2y3zxiKZT4H1hNSIKMTc=
-----END RSA PRIVATE KEY-----
faye-websocket-ruby-0.11.0/spec/spec_helper.rb 0000664 0000000 0000000 00000000330 13711041666 0021231 0 ustar 00root root 0000000 0000000 require 'bundler/setup'
require 'rspec/em'
require File.expand_path('../../lib/faye/websocket', __FILE__)
require File.expand_path('../echo_server', __FILE__)
require File.expand_path('../proxy_server', __FILE__)