Class: WebsocketTD::Websocket

Inherits:
Object
  • Object
show all
Defined in:
lib/websocket_td.rb

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(host, path, query, secure = false, port = nil) ⇒ Websocket

host

Host of request. Required if no :url param was provided.

path

Path of request. Should start with ‘/’. Default: ‘/’

query

Query for request. Should be in format “aaa=bbb&ccc=ddd”

secure

Defines protocol to use. If true then wss://, otherwise ws://. This option will not change default port - it should be handled by programmer.

port

Port of request. Default: nil



20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
# File 'lib/websocket_td.rb', line 20

def initialize(host, path, query, secure = false, port = nil)
  if port == nil
    port = secure ? 443 : 80
  end

  @handshake = WebSocket::Handshake::Client.new({
                                                    :host   => host,
                                                    :port   => port,
                                                    :secure => secure,
                                                    :path   => path,
                                                    :query  => query
                                                })

  @read_buffer = 2048
  @auto_pong   = true
  @closed      = false
  @opened      = false

  @on_open    = lambda {}
  @on_close   = lambda { |message|}
  @on_ping    = lambda { |message|}
  @on_error   = lambda { |error|}
  @on_message = lambda { |message|}

  tcp_socket = TCPSocket.new(host, port)
  if secure
    @socket = OpenSSL::SSL::SSLSocket.new(tcp_socket)
    @socket.connect
  else
    @socket = tcp_socket
  end

  @socket.write @handshake.to_s
  buf     = ''
  headers = ''
  reading = true

  while reading
    begin
      if @handshake.finished?
        init_messaging
        #don't stop reading until after init_message to guarantee @read_thread != nil for a successful connection
        reading = false
        @opened =true
        fire_on_open
      else
        #do non blocking reads on headers - 1 byte at a time
        buf.concat(@socket.read_nonblock(1))
        #\r\n\r\n i.e. a blank line, separates headers from body
        idx = buf.index(/\r\n\r\n/m)
        if idx != nil
          headers = buf.slice!(0..idx + 8) #+8 to include the blank line separator
          @handshake << headers            #parse headers

          if @handshake.finished? && !@handshake.valid?
            fire_on_error(ConnectError.new('Server responded with an invalid handshake'))
            fire_on_close() #close if handshake is not valid
          end

          if @handshake.finished?
            @active = true
            buf     = '' #clean up
            headers = ''
          end
        end
      end
    rescue IO::WaitReadable
      # ignored
    rescue IO::WaitWritable
      # ignored
    end
  end
  puts headers
end

Instance Attribute Details

#activeObject (readonly)

Returns the value of attribute active.



95
96
97
# File 'lib/websocket_td.rb', line 95

def active
  @active
end

#auto_pongObject

Returns the value of attribute auto_pong.



95
96
97
# File 'lib/websocket_td.rb', line 95

def auto_pong
  @auto_pong
end

#on_error=(value) ⇒ Object (writeonly)

:on_open, :on_close



96
97
98
# File 'lib/websocket_td.rb', line 96

def on_error=(value)
  @on_error = value
end

#on_message=(value) ⇒ Object (writeonly)

:on_open, :on_close



96
97
98
# File 'lib/websocket_td.rb', line 96

def on_message=(value)
  @on_message = value
end

#on_ping=(value) ⇒ Object (writeonly)

:on_open, :on_close



96
97
98
# File 'lib/websocket_td.rb', line 96

def on_ping=(value)
  @on_ping = value
end

#read_bufferObject

Returns the value of attribute read_buffer.



95
96
97
# File 'lib/websocket_td.rb', line 95

def read_buffer
  @read_buffer
end

#read_threadObject (readonly)

Returns the value of attribute read_thread.



95
96
97
# File 'lib/websocket_td.rb', line 95

def read_thread
  @read_thread
end

#socketObject (readonly)

Returns the value of attribute socket.



95
96
97
# File 'lib/websocket_td.rb', line 95

def socket
  @socket
end

Instance Method Details

#determine_message_type(message) ⇒ Object



137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
# File 'lib/websocket_td.rb', line 137

def determine_message_type(message)
  if message.type == :binary || message.type == :text
    fire_on_message(message)
  elsif message.type == :ping
    if @auto_pong
      send(message.data, :pong)
    end
    fire_on_ping(message)
  elsif message.type == :pong
    fire_on_error(WsProtocolError.new('Invalid type pong received'))
  elsif message.type == :close
    fire_on_close(message)
  else
    fire_on_error(BadMessageTypeError.new("An unknown message type was received #{message.data}"))
  end
end

#fire_on_close(message = nil) ⇒ Object



184
185
186
187
188
189
# File 'lib/websocket_td.rb', line 184

def fire_on_close(message = nil)
  @active = false
  @closed = true
  @on_close.call(message) unless @on_close == nil
  @socket.close
end

#fire_on_error(error) ⇒ Object



180
181
182
# File 'lib/websocket_td.rb', line 180

def fire_on_error(error)
  @on_error.call(error) unless @on_error == nil
end

#fire_on_message(message) ⇒ Object



172
173
174
# File 'lib/websocket_td.rb', line 172

def fire_on_message(message)
  @on_message.call(message) unless @on_message == nil
end

#fire_on_openObject



176
177
178
# File 'lib/websocket_td.rb', line 176

def fire_on_open
  @on_open.call() unless @on_open == nil
end

#fire_on_ping(message) ⇒ Object



168
169
170
# File 'lib/websocket_td.rb', line 168

def fire_on_ping(message)
  @on_ping.call(message) unless @on_ping == nil
end

#init_messagingObject

Use one thread to perform blocking read on the socket



113
114
115
116
117
118
119
# File 'lib/websocket_td.rb', line 113

def init_messaging
  if @read_thread == nil
    @read_thread = Thread.new do
      read_loop
    end
  end
end

#on_close=(p) ⇒ Object



105
106
107
108
109
110
# File 'lib/websocket_td.rb', line 105

def on_close=(p)
  @on_close = p
  if @closed
    fire_on_close
  end
end

#on_open=(p) ⇒ Object



98
99
100
101
102
103
# File 'lib/websocket_td.rb', line 98

def on_open=(p)
  @on_open = p
  if @opened
    fire_on_open
  end
end

#read_loopObject



121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
# File 'lib/websocket_td.rb', line 121

def read_loop
  frame = WebSocket::Frame::Incoming::Client.new(:version => @handshake.version)
  while @active
    if @socket.closed?
      @active = false
      fire_on_close
    else
      frame << @socket.readpartial(@read_buffer)
      if (message = frame.next) != nil
        #"text", "binary", "ping", "pong" and "close" (according to websocket/base.rb)
        determine_message_type(message)
      end
    end
  end
end

#send(data, type = :text) ⇒ Object



155
156
157
158
159
160
161
162
163
164
165
166
# File 'lib/websocket_td.rb', line 155

def send(data, type = :text)
  frame = WebSocket::Frame::Outgoing::Client.new(:version => @handshake.version, :data => data, :type => type)
  begin
    @socket.write frame #_nonblock
    @socket.flush
  rescue Errno::EPIPE => ce
    fire_on_error(ce)
    fire_on_close
  rescue Exception => e
    fire_on_error(e)
  end
end