Class: ThinEM::Websocket::Connection

Inherits:
Object
  • Object
show all
Defined in:
lib/thin-em-websocket.rb

Overview

connection SHIM, so we only override minimal amounts of thin

Constant Summary collapse

ENCODING_SUPPORTED =

Cache encodings since it’s moderately expensive to look them up each time

"string".respond_to?(:force_encoding)
UTF8 =
Encoding.find("UTF-8")
BINARY =
Encoding.find("BINARY")

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(connection) ⇒ Connection

Returns a new instance of Connection.



44
45
46
47
48
49
50
51
52
# File 'lib/thin-em-websocket.rb', line 44

def initialize(connection)
  @connection = connection
  @logger = Class.new do
    def error(m); end
    def warn(m); end
    def debug(m); end
    def info(m); end
  end.new
end

Instance Attribute Details

#max_frame_sizeObject

Returns the maximum frame size which this connection is configured to accept. This can be set globally or on a per connection basis, and defaults to a value of 10MB if not set.

The behaviour when a too large frame is received varies by protocol, but in the newest protocols the connection will be closed with the correct close code (1009) immediately after receiving the frame header



212
213
214
# File 'lib/thin-em-websocket.rb', line 212

def max_frame_size
  @max_frame_size || EventMachine::WebSocket.max_frame_size
end

Instance Method Details

#close_connection_after_writingObject



216
217
218
# File 'lib/thin-em-websocket.rb', line 216

def close_connection_after_writing()
  @connection.close_connection_after_writing()
end

#logger=(logger) ⇒ Object



54
55
56
# File 'lib/thin-em-websocket.rb', line 54

def logger=(logger)
  @logger = logger
end

#onclose(&blk) ⇒ Object



17
# File 'lib/thin-em-websocket.rb', line 17

def onclose(&blk);    @onclose = blk;   end

#onerror(&blk) ⇒ Object



18
# File 'lib/thin-em-websocket.rb', line 18

def onerror(&blk);    @onerror = blk;   end

#onmessage(&blk) ⇒ Object



19
# File 'lib/thin-em-websocket.rb', line 19

def onmessage(&blk);  @onmessage = blk; end

#onopen(&blk) ⇒ Object

define WebSocket callbacks



16
# File 'lib/thin-em-websocket.rb', line 16

def onopen(&blk);     @onopen = blk;    end

#onping(&blk) ⇒ Object



20
# File 'lib/thin-em-websocket.rb', line 20

def onping(&blk);     @onping = blk;    end

#onpong(&blk) ⇒ Object



21
# File 'lib/thin-em-websocket.rb', line 21

def onpong(&blk);     @onpong = blk;    end

#pending_upgrade?Boolean

Returns:

  • (Boolean)


89
90
91
# File 'lib/thin-em-websocket.rb', line 89

def pending_upgrade?
  @handler.nil? && @sent_upgrade
end

#ping(body = '') ⇒ Object

Send a ping to the client. The client must respond with a pong.

In the case that the client is running a WebSocket draft < 01, false is returned since ping & pong are not supported



167
168
169
170
171
172
173
# File 'lib/thin-em-websocket.rb', line 167

def ping(body = '')
  if @handler
    @handler.pingable? ? @handler.send_frame(:ping, body) && true : false
  else
    raise WebSocketError, "Cannot ping before onopen callback"
  end
end

#pingable?Boolean

Test whether the connection is pingable (i.e. the WebSocket draft in use is >= 01)

Returns:

  • (Boolean)


191
192
193
194
195
196
197
# File 'lib/thin-em-websocket.rb', line 191

def pingable?
  if @handler
    @handler.pingable?
  else
    raise WebSocketError, "Cannot test whether pingable before onopen callback"
  end
end

#pong(body = '') ⇒ Object

Send an unsolicited pong message, as allowed by the protocol. The client is not expected to respond to this message.

em-websocket automatically takes care of sending pong replies to incoming ping messages, as the protocol demands.



181
182
183
184
185
186
187
# File 'lib/thin-em-websocket.rb', line 181

def pong(body = '')
  if @handler
    @handler.pingable? ? @handler.send_frame(:pong, body) && true : false
  else
    raise WebSocketError, "Cannot ping before onopen callback"
  end
end

#receive_data(data) ⇒ Object



109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
# File 'lib/thin-em-websocket.rb', line 109

def receive_data(data)
  begin
    @logger.info("Got Socket Data (l: #{data.length})")
    @handler.receive_data(data)
  rescue EventMachine::WebSocket::HandshakeError => e
    @logger.warn("Web Socket Failed to handshake")
    trigger_on_error(e)
    # Errors during the handshake require the connection to be aborted
    abort
  rescue EventMachine::WebSocket::WSProtocolError => e
    @logger.warn("Web Socket Protocol Error")
    trigger_on_error(e)
    close_websocket_private(e.code)
  rescue => e
      # These are application errors - raise unless onerror defined
    trigger_on_error(e) || raise(e)
    # There is no code defined for application errors, so use 3000
    # (which is reserved for frameworks)
    close_websocket_private(3000)
  end
end

#send(data) ⇒ Object

Send a WebSocket text frame.

A WebSocketError may be raised if the connection is in an opening or a closing state, or if the passed in data is not valid UTF-8



136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
# File 'lib/thin-em-websocket.rb', line 136

def send(data)
  # If we're using Ruby 1.9, be pedantic about encodings
  if ENCODING_SUPPORTED
    # Also accept ascii only data in other encodings for convenience
    unless (data.encoding == UTF8 && data.valid_encoding?) || data.ascii_only?
      raise WebSocketError, "Data sent to WebSocket must be valid UTF-8 but was #{data.encoding} (valid: #{data.valid_encoding?})"
    end
    # This labels the encoding as binary so that it can be combined with
    # the BINARY framing
    data.force_encoding(BINARY)
  else
    # TODO: Check that data is valid UTF-8
  end

  if @handler
    @handler.send_text_frame(data)
  else
    raise WebSocketError, "Cannot send data before onopen callback"
  end

  # Revert data back to the original encoding (which we assume is UTF-8)
  # Doing this to avoid duping the string - there may be a better way
  data.force_encoding(UTF8) if ENCODING_SUPPORTED
  return nil
end

#send_data(data) ⇒ Object



99
100
101
102
103
104
105
106
107
# File 'lib/thin-em-websocket.rb', line 99

def send_data(data)
  if @sent_upgrade && !@upgrade_stripped
    # strip it
    raise EventMachine::WebSocket::HandshakeError if @handshake_76_without_verify != data[0..@handshake_76_without_verify.length-1]
    data = data[@handshake_76_without_verify.length..-1]
    @upgrade_stripped = true
  end
  @connection.send_data(data)
end

#stateObject



200
201
202
# File 'lib/thin-em-websocket.rb', line 200

def state
  @handler ? @handler.state : :handshake
end

#trigger_on_closeObject



29
30
31
# File 'lib/thin-em-websocket.rb', line 29

def trigger_on_close
  @onclose.call if @onclose
end

#trigger_on_error(reason) ⇒ Object



38
39
40
41
42
# File 'lib/thin-em-websocket.rb', line 38

def trigger_on_error(reason)
  return false unless @onerror
  @onerror.call(reason)
  true
end

#trigger_on_message(msg) ⇒ Object



23
24
25
# File 'lib/thin-em-websocket.rb', line 23

def trigger_on_message(msg)
  @onmessage.call(msg) if @onmessage
end

#trigger_on_openObject



26
27
28
# File 'lib/thin-em-websocket.rb', line 26

def trigger_on_open
  @onopen.call if @onopen
end

#trigger_on_ping(data) ⇒ Object



32
33
34
# File 'lib/thin-em-websocket.rb', line 32

def trigger_on_ping(data)
  @onping.call(data) if @onping
end

#trigger_on_pong(data) ⇒ Object



35
36
37
# File 'lib/thin-em-websocket.rb', line 35

def trigger_on_pong(data)
  @onpong.call(data) if @onpong
end

#upgrade_websocketObject



62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
# File 'lib/thin-em-websocket.rb', line 62

def upgrade_websocket
  return if @handler
  @handler = EM::WebSocket::HandlerFactory.build(self, @connection.ws_buffer, false, nil)
  unless @handler
    # see: https://github.com/learnboost/socket.io/commit/9982232032771574ceb68e2bccee4e43fd5af887#diff-0
    # hixie-76 behind HAProxy gets a bit messy, we need to send the header first to unblock the stream
    if !@sent_upgrade && @connection.ws_buffer =~ /sec-websocket-key1/i
      @logger.info("WebSocket: attempting hixie 76 hack")

      fake_buffer = @connection.ws_buffer.dup
      fake_buffer << "12345678"
      (header, remains) = fake_buffer.split("\r\n\r\n", 2)
      fake_handler = EM::WebSocket::HandlerFactory.build(self, fake_buffer, false, nil)

      @handshake_76_without_verify = fake_handler.handshake[0..-17]
      send_data(@handshake_76_without_verify)
      @sent_upgrade = true
    end
  end
  @connection.set_comm_inactivity_timeout(0) if @handler
  @handler.run if @handler
end

#upgraded?Boolean

Returns:

  • (Boolean)


85
86
87
# File 'lib/thin-em-websocket.rb', line 85

def upgraded?
  !@handler.nil?
end

#websocket?Boolean

Returns:

  • (Boolean)


58
59
60
# File 'lib/thin-em-websocket.rb', line 58

def websocket?
  true
end