- Notifications
You must be signed in to change notification settings - Fork97
Standards-compliant WebSocket client and server
License
faye/faye-websocket-ruby
Folders and files
| Name | Name | Last commit message | Last commit date | |
|---|---|---|---|---|
Repository files navigation
This is a general-purpose WebSocket implementation extracted from theFaye project. It provides classes for easily buildingWebSocket servers and clients in Ruby. It does not provide a server itself, butrather makes it easy to handle WebSocket connections within an existingRack application. It does not provide any abstractionother than the standardWebSocketAPI.
It also provides an abstraction for handlingEventSourceconnections, which are one-way connections that allow the server to push data tothe client. They are based on streaming HTTP responses and can be easier toaccess via proxies than WebSockets.
The following web servers are supported. Other servers that implement therack.hijack API should also work.
- Goliath
- Phusion Passenger >= 4.0 with nginx >= 1.4
- Puma >= 2.0
- Rainbows
- Thin
$ gem install faye-websocketYou can handle WebSockets on the server side by listening for requests using theFaye::WebSocket.websocket? method, and creating a new socket for the request.This socket object exposes the usual WebSocket methods for receiving and sendingmessages. For example this is how you'd implement an echo server:
# app.rbrequire'faye/websocket'App=lambdado |env|ifFaye::WebSocket.websocket?(env)ws=Faye::WebSocket.new(env)ws.on:messagedo |event|ws.send(event.data)endws.on:closedo |event|p[:close,event.code,event.reason]ws=nilend# Return async Rack responsews.rack_responseelse# Normal HTTP request[200,{'Content-Type'=>'text/plain'},['Hello']]endend
Note that under certain circumstances (notably a draft-76 client connectingthrough an HTTP proxy), the WebSocket handshake will not be complete after youcallFaye::WebSocket.new because the server will not have received the entirehandshake from the client yet. In this case, calls tows.send will buffer themessage in memory until the handshake is complete, at which point any bufferedmessages will be sent to the client.
If you need to detect when the WebSocket handshake is complete, you can use theonopen event.
If the connection's protocol version supports it, you can callws.ping() tosend a ping message and wait for the client's response. This method takes amessage string, and an optional callback that fires when a matching pong messageis received. It returnstrue if and only if a ping message was sent. If theclient does not support ping/pong, this method sends no data and returnsfalse.
ws.ping'Mic check, one, two'do# fires when pong is receivedend
The client supports both the plain-textws protocol and the encryptedwssprotocol, and has exactly the same interface as a socket you would use in a webbrowser. On the wire it identifies itself ashybi-13.
require'faye/websocket'require'eventmachine'EM.run{ws=Faye::WebSocket::Client.new('ws://www.example.com/')ws.on:opendo |event|p[:open]ws.send('Hello, world!')endws.on:messagedo |event|p[:message,event.data]endws.on:closedo |event|p[:close,event.code,event.reason]ws=nilend}
The WebSocket client also lets you inspect the status and headers of thehandshake response via itsstatus andheaders methods.
To connect via a proxy, set theproxy option to the HTTP origin of the proxy,including any authorization information and custom headers you require:
ws=Faye::WebSocket::Client.new('ws://www.example.com/',[],{:proxy=>{:origin=>'http://username:password@proxy.example.com',:headers=>{'User-Agent'=>'ruby'}}})
The WebSocket protocol allows peers to select and identify the applicationprotocol to use over the connection. On the client side, you can set whichprotocols the client accepts by passing a list of protocol names when youconstruct the socket:
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 serversupports after the other constructor arguments:
ws=Faye::WebSocket.new(env,['irc','amqp'])
If the client and server agree on a protocol, both the client- and server-sidesocket objects expose the selected protocol through thews.protocol property.
faye-websocket is based on thewebsocket-extensionsframework that allows extensions to be negotiated via theSec-WebSocket-Extensions header. To add extensions to a connection, pass anarray of extensions to the:extensions option. For example, to addpermessage-deflate:
require'permessage_deflate'ws=Faye::WebSocket.new(env,[],:extensions=>[PermessageDeflate])
Both the server- and client-side classes allow an options hash to be passed inat initialization time, for example:
ws=Faye::WebSocket.new(env,protocols,options)ws=Faye::WebSocket::Client.new(url,protocols,options)
protocols as an array of subprotocols as described above, ornil.optionsis an optional hash containing any of these keys:
:extensions- an array ofwebsocket-extensionscompatible extensions, as described above:headers- a hash containing key-value pairs representing HTTP headers to besent during the handshake process:max_length- the maximum allowed size of incoming message frames, in bytes.The default value is2^26 - 1, or 1 byte short of 64 MiB.:ping- an integer that sets how often the WebSocket should send pingframes, measured in seconds:tls- a hash containing key-value pairs for specifying TLS parameters.These are passed along to EventMachine and you can findmore details here
Starting with version 0.11.0,Faye::WebSocket::Client will verify the servercertificate forwss connections. This is not the default behaviour forEventMachine's TLS interface, and so our defaults for the:tls option are alittle different.
First,:verify_peer is enabled by default. Our implementation checks that thechain of certificates sent by the server is trusted by your root certificates,and that the final certificate's hostname matches the hostname in the requestURL.
By default, we use your system's root certificate store by invokingOpenSSL::X509::Store#set_default_paths. If you want to use a different set ofroot certificates, you can pass them via the:root_cert_file option, whichtakes a path or an array of paths to the certificates you want to use.
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 tofalse.
ws=Faye::WebSocket::Client.new('wss://example.com/',[],:tls=>{:verify_peer=>false})
Both the server- and client-sideWebSocket 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. Eventhas one attribute,data, which is either aString(for text frames) oranArrayof unsigned integers, i.e. integers in the range0..255(forbinary frames).on(:error) { |event| }fires when there is a protocol error due to baddata sent by the other peer. This event is purely informational, you do notneed to implement error recovery.on(:close) { |event| }fires when either the client or the server closesthe connection. Event has two optional attributes,codeandreason, that expose the status code and message sent by the peer thatclosed the connection.send(message)accepts either aStringor anArrayof byte-sizedintegers and sends a text or binary message over the connection to the otherpeer; binary data must be encoded as anArray.ping(message, &callback)sends a ping frame with an optional message andfires the callback when a matching pong is received.close(code, reason)closes the connection, sending the given status codeand reason text, both of which are optional.versionis a string containing the version of theWebSocketprotocolthe connection is using.protocolis a string (which may be empty) identifying the subprotocolthe socket is using.
EventSource connections provide a very similar interface, although because theyonly allow the server to send data to the client, there is noonmessage API.EventSource allows the server to push text messages to the client, where eachmessage has an optional event-type and ID.
# app.rbrequire'faye/websocket'App=lambdado |env|ifFaye::EventSource.eventsource?(env)es=Faye::EventSource.new(env)p[:open,es.url,es.last_event_id]# Periodically send messagesloop=EM.add_periodic_timer(1){es.send('Hello')}es.on:closedo |event|EM.cancel_timer(loop)es=nilend# Return async Rack responsees.rack_responseelse# Normal HTTP request[200,{'Content-Type'=>'text/plain'},['Hello']]endend
Thesend method takes two optional parameters,:event and:id. The defaultevent-type is'message' with no ID. For example, to send anotificationevent with ID99:
es.send('Breaking News!',:event=>'notification',:id=>'99')
TheEventSource object exposes the following properties:
urlis a string containing the URL the client used to create theEventSource.last_event_idis a string containing the last event ID received by theclient. You can use this when the client reconnects after a dropped connectionto determine which messages need resending.
When you initialize an EventSource withFaye::EventSource.new, you can passconfiguration options after theenv parameter. Available options are:
:headersis a hash containing custom headers to be set on theEventSource response.:retryis a number that tells the client how long (in seconds) it shouldwait after a dropped connection before attempting to reconnect.:pingis a number that tells the server how often (in seconds) to send'ping' packets to the client to keep the connection open, to defeat timeoutsset by proxies. The client will ignore these messages.
For example, this creates a connection that allows access from any origin, pingsevery 15 seconds and is retryable every 10 seconds if the connection is broken:
es=Faye::EventSource.new(es,:headers=>{'Access-Control-Allow-Origin'=>'*'},:ping=>15,:retry=>10)
You can send a ping message at any time by callinges.ping. Unlike WebSocketthe client does not send a response to this; it is merely to send some data overthe wire to keep the connection alive.
The following describes how to run a WebSocket application using all oursupported web servers.
If you use Thin to serve your application you need to include this line afterloadingfaye/websocket:
Faye::WebSocket.load_adapter('thin')
Thin can be started via the command line if you've set up aconfig.ru file foryour application:
$ thin start -R config.ru -p 9292Or, you can userackup. In development mode, this adds middlewares that don'twork with async apps, so you must start it in production mode:
$ rackup config.ru -s thin -E production -p 9292It can also be started using theRack::Handler interface common to many Rubyservers. You can configure Thin further in a block passed torun:
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=trueend
faye-websocket requires either Passenger for Nginx or Passenger Standalone.Apache doesn't work well with WebSockets at this time.You do not need any special configuration to make faye-websocket work, itshould work out of the box on Passenger provided you use at least Passenger4.0.
However, you do need to insert the following code inconfig.ru for optimalWebSocket performance in Passenger. This isdocumented in the Passenger manual.
ifdefined?(PhusionPassenger)PhusionPassenger.advertised_concurrency_level=0end
Run your app on Passenger for Nginx by creating a virtual host entry whichpoints 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 9292More information can be found onthe Passengerwebsite.
Puma has a command line interface for starting your application:
$ puma config.ru -p 9292Or, you can userackup. In development mode, this adds middlewares that don'twork with async apps, so you must start it in production mode:
$ rackup config.ru -s puma -E production -p 9292If you're using version 4.4 or lower of Rainbows, you need to run it with theEventMachine backend and enable the adapter. Put this in yourrainbows.conffile:
Rainbows!{use:EventMachine}
And make sure you load the adapter in your application:
Faye::WebSocket.load_adapter('rainbows')
Version 4.5 of Rainbows does not need this adapter.
You can run yourconfig.ru file from the command line. Again,Rack::Lintwill complain unless you put the application in production mode.
$ rainbows config.ru -c path/to/rainbows.conf -E production -p 9292If you use Goliath to server your application you need to include this lineafter loadingfaye/websocket:
Faye::WebSocket.load_adapter('goliath')
Goliath can be made to run arbitrary Rack apps by delegating to them from aGoliath::API instance. A simple server looks like this:
require'goliath'require'./app'Faye::WebSocket.load_adapter('goliath')classEchoServer <Goliath::APIdefresponse(env)App.call(env)endend
Faye::WebSocket can also be used inline within a Goliath app:
require'goliath'require'faye/websocket'Faye::WebSocket.load_adapter('goliath')classEchoServer <Goliath::APIdefresponse(env)ws=Faye::WebSocket.new(env)ws.on:messagedo |event|ws.send(event.data)endws.rack_responseendend
About
Standards-compliant WebSocket client and server
Resources
License
Code of conduct
Uh oh!
There was an error while loading.Please reload this page.
Stars
Watchers
Forks
Packages0
Uh oh!
There was an error while loading.Please reload this page.