Class: FTW::WebSocket::Rack

Inherits:
Object
  • Object
show all
Includes:
Constants
Defined in:
lib/ftw/websocket/rack.rb

Overview

A websocket helper for Rack

An example with Sinatra:

get "/websocket/echo" do
  ws = FTW::WebSocket::Rack.new(env)
  stream(:keep_open) do |out|
    ws.each do |payload|
      # 'payload' is the text payload of a single websocket message
      # publish it back to the client
      ws.publish(payload)
    end
  end
  ws.rack_response
end

Constant Summary

Constants included from Constants

Constants::OPCODE_BINARY, Constants::OPCODE_CLOSE, Constants::OPCODE_CONTINUATION, Constants::OPCODE_PING, Constants::OPCODE_PONG, Constants::OPCODE_TEXT, Constants::WEBSOCKET_ACCEPT_UUID

Instance Method Summary collapse

Constructor Details

#initialize(rack_env) ⇒ Rack

Create a new websocket rack helper… thing.

Parameters:

  • rack_env

    the ‘env’ bit given to your Rack application



29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
# File 'lib/ftw/websocket/rack.rb', line 29

def initialize(rack_env)
  @env = rack_env
  @handshake_errors = []

  # RFC6455 section 4.2.1 bullet 3
  expect_equal("websocket", @env["HTTP_UPGRADE"],
               "The 'Upgrade' header must be set to 'websocket'")
  # RFC6455 section 4.2.1 bullet 4
  expect_equal("Upgrade", @env["HTTP_CONNECTION"],
               "The 'Connection' header must be set to 'Upgrade'")
  # RFC6455 section 4.2.1 bullet 6
  expect_equal("13", @env["HTTP_SEC_WEBSOCKET_VERSION"],
               "Sec-WebSocket-Version must be set to 13")

  # RFC6455 section 4.2.1 bullet 5
  @key = @env["HTTP_SEC_WEBSOCKET_KEY"] 

  @parser = FTW::WebSocket::Parser.new
end

Instance Method Details

#eachObject

Enumerate each websocket payload (message).

The payload of each message will be yielded to the block.

Example:

ws.each do |payload|
  puts "Received: #{payload}"
end


100
101
102
103
104
105
106
107
108
# File 'lib/ftw/websocket/rack.rb', line 100

def each
  connection = @env["ftw.connection"]
  while true
    data = connection.read(16384)
    @parser.feed(data) do |payload|
      yield payload if !payload.nil?
    end
  end
end

#publish(message) ⇒ Object

Publish a message over this websocket.

Parameters:

  • message

    Publish a string message to the websocket.



113
114
115
116
# File 'lib/ftw/websocket/rack.rb', line 113

def publish(message)
  writer = FTW::WebSocket::Writer.singleton
  writer.write_text(@env["ftw.connection"], message)
end

#rack_responsenumber, ...

Get the response Rack is expecting.

If this was a valid websocket request, it will return a response that completes the HTTP portion of the websocket handshake.

If this was an invalid websocket request, it will return a 400 status code and descriptions of what failed in the body of the response.

Returns:

  • (number, hash, body)


71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
# File 'lib/ftw/websocket/rack.rb', line 71

def rack_response
  if valid?
    # Return the status, headers, body that is expected.
    sec_accept = @key + WEBSOCKET_ACCEPT_UUID
    sec_accept_hash = Digest::SHA1.base64digest(sec_accept)

    headers = {
      "Upgrade" => "websocket",
      "Connection" => "Upgrade",
      "Sec-WebSocket-Accept" => sec_accept_hash
    }
    # See RFC6455 section 4.2.2
    return 101, headers, nil
  else
    # Invalid request, tell the client why.
    return 400, { "Content-Type" => "text/plain" },
      @handshake_errors.map { |m| "#{m}#{CRLF}" }
  end
end

#valid?Boolean

Is this a valid handshake?

Returns:

  • (Boolean)


57
58
59
# File 'lib/ftw/websocket/rack.rb', line 57

def valid?
  return @handshake_errors.empty?
end