Class: Protocol::WebSocket::Connection

Inherits:
Object
  • Object
show all
Defined in:
lib/protocol/websocket/connection.rb

Overview

Wraps a framer and implements for implementing connection specific interactions like reading and writing text.

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(framer, mask: nil, **options) ⇒ Connection

Returns a new instance of Connection.



16
17
18
19
20
21
22
23
24
25
26
27
# File 'lib/protocol/websocket/connection.rb', line 16

def initialize(framer, mask: nil, **options)
	@framer = framer
	@mask = mask
	
	@state = :open
	@frames = []
	
	@reserved = Frame::RESERVED
	
	@reader = self
	@writer = self
end

Instance Attribute Details

#framerObject (readonly)

The framer which is used for reading and writing frames.



30
31
32
# File 'lib/protocol/websocket/connection.rb', line 30

def framer
  @framer
end

#framesObject

Buffered frames which form part of a complete message.



39
40
41
# File 'lib/protocol/websocket/connection.rb', line 39

def frames
  @frames
end

#maskObject (readonly)

The (optional) mask which is used when generating frames.



33
34
35
# File 'lib/protocol/websocket/connection.rb', line 33

def mask
  @mask
end

#readerObject

Returns the value of attribute reader.



41
42
43
# File 'lib/protocol/websocket/connection.rb', line 41

def reader
  @reader
end

#reservedObject (readonly)

The allowed reserved bits:



36
37
38
# File 'lib/protocol/websocket/connection.rb', line 36

def reserved
  @reserved
end

#writerObject

Returns the value of attribute writer.



42
43
44
# File 'lib/protocol/websocket/connection.rb', line 42

def writer
  @writer
end

Instance Method Details

#closeObject

Immediately transition the connection to the closed state and close the underlying connection.



84
85
86
87
88
# File 'lib/protocol/websocket/connection.rb', line 84

def close(...)
	close!(...)
	
	@framer.close
end

#close!Object

If not already closed, transition the connection to the closed state and send a close frame.



65
66
67
68
69
70
71
72
73
74
75
76
77
# File 'lib/protocol/websocket/connection.rb', line 65

def close!(...)
	unless @state == :closed
		@state = :closed
		
		begin
			send_close(...)
		rescue
			# Ignore errors.
		end
	end
	
	return self
end

#closed?Boolean

Returns:

  • (Boolean)


79
80
81
# File 'lib/protocol/websocket/connection.rb', line 79

def closed?
	@state == :closed
end

#flushObject



54
55
56
# File 'lib/protocol/websocket/connection.rb', line 54

def flush
	@framer.flush
end

#open!Object



58
59
60
61
62
# File 'lib/protocol/websocket/connection.rb', line 58

def open!
	@state = :open
	
	return self
end

#pack_binary_frame(buffer, **options) ⇒ Object



191
192
193
194
195
196
# File 'lib/protocol/websocket/connection.rb', line 191

def pack_binary_frame(buffer, **options)
	frame = BinaryFrame.new(mask: @mask)
	frame.pack(buffer)
	
	return frame
end

#pack_text_frame(buffer, **options) ⇒ Object



180
181
182
183
184
185
# File 'lib/protocol/websocket/connection.rb', line 180

def pack_text_frame(buffer, **options)
	frame = TextFrame.new(mask: @mask)
	frame.pack(buffer)
	
	return frame
end

#read(**options) ⇒ Object

Read a message from the connection.



233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
# File 'lib/protocol/websocket/connection.rb', line 233

def read(**options)
	@framer.flush
	
	while read_frame
		if @frames.last&.finished?
			frames = @frames
			@frames = []
			
			buffer = @reader.unpack_frames(frames, **options)
			return frames.first.read_message(buffer)
		end
	end
rescue ProtocolError => error
	close(error.code, error.message)
	raise
end

#read_frameObject



90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
# File 'lib/protocol/websocket/connection.rb', line 90

def read_frame
	return nil if closed?
	
	frame = @framer.read_frame
	
	unless (frame.flags & @reserved).zero?
		raise ProtocolError, "Received frame with reserved flags set!"
	end
	
	yield frame if block_given?
	
	frame.apply(self)
	
	return frame
rescue ProtocolError => error
	close(error.code, error.message)
	raise
rescue => error
	close(Error::PROTOCOL_ERROR, error.message)
	raise
end

#receive_binary(frame) ⇒ Object



126
127
128
129
130
131
132
# File 'lib/protocol/websocket/connection.rb', line 126

def receive_binary(frame)
	if @frames.empty?
		@frames << frame
	else
		raise ProtocolError, "Received binary, but expecting continuation!"
	end
end

#receive_close(frame) ⇒ Object



142
143
144
145
146
147
148
149
150
151
# File 'lib/protocol/websocket/connection.rb', line 142

def receive_close(frame)
	code, reason = frame.unpack
	
	# On receiving a close frame, we must enter the closed state:
	close!(code, reason)
	
	if code and code != Error::NO_ERROR
		raise ClosedError.new reason, code
	end
end

#receive_continuation(frame) ⇒ Object



134
135
136
137
138
139
140
# File 'lib/protocol/websocket/connection.rb', line 134

def receive_continuation(frame)
	if @frames.any?
		@frames << frame
	else
		raise ProtocolError, "Received unexpected continuation!"
	end
end

#receive_frame(frame) ⇒ Object

Raises:



176
177
178
# File 'lib/protocol/websocket/connection.rb', line 176

def receive_frame(frame)
	raise ProtocolError, "Unhandled frame: #{frame}"
end

#receive_ping(frame) ⇒ Object



164
165
166
167
168
169
170
# File 'lib/protocol/websocket/connection.rb', line 164

def receive_ping(frame)
	if @state != :closed
		write_frame(frame.reply(mask: @mask))
	else
		raise ProtocolError, "Cannot receive ping in state #{@state}"
	end
end

#receive_pong(frame) ⇒ Object



172
173
174
# File 'lib/protocol/websocket/connection.rb', line 172

def receive_pong(frame)
	# Ignore.
end

#receive_text(frame) ⇒ Object



118
119
120
121
122
123
124
# File 'lib/protocol/websocket/connection.rb', line 118

def receive_text(frame)
	if @frames.empty?
		@frames << frame
	else
		raise ProtocolError, "Received text, but expecting continuation!"
	end
end

#reserve!(bit) ⇒ Object



44
45
46
47
48
49
50
51
52
# File 'lib/protocol/websocket/connection.rb', line 44

def reserve!(bit)
	if (@reserved & bit).zero?
		raise ArgumentError, "Unable to use #{bit}!"
	end
	
	@reserved &= ~bit
	
	return true
end

#send_binary(buffer, **options) ⇒ Object



198
199
200
# File 'lib/protocol/websocket/connection.rb', line 198

def send_binary(buffer, **options)
	write_frame(@writer.pack_binary_frame(buffer, **options))
end

#send_close(code = Error::NO_ERROR, reason = "") ⇒ Object

Send a control frame with data containing a specified control sequence to begin the closing handshake. Does not close the connection, until the remote end responds with a close frame.



203
204
205
206
207
208
209
# File 'lib/protocol/websocket/connection.rb', line 203

def send_close(code = Error::NO_ERROR, reason = "")
	frame = CloseFrame.new(mask: @mask)
	frame.pack(code, reason)
	
	self.write_frame(frame)
	self.flush
end

#send_ping(data = "") ⇒ Object



153
154
155
156
157
158
159
160
161
162
# File 'lib/protocol/websocket/connection.rb', line 153

def send_ping(data = "")
	if @state != :closed
		frame = PingFrame.new(mask: @mask)
		frame.pack(data)
		
		write_frame(frame)
	else
		raise ProtocolError, "Cannot send ping in state #{@state}"
	end
end

#send_text(buffer, **options) ⇒ Object



187
188
189
# File 'lib/protocol/websocket/connection.rb', line 187

def send_text(buffer, **options)
	write_frame(@writer.pack_text_frame(buffer, **options))
end

#unpack_frames(frames) ⇒ Object

The default implementation for reading a message buffer.



227
228
229
# File 'lib/protocol/websocket/connection.rb', line 227

def unpack_frames(frames)
	frames.map(&:unpack).join("")
end

#write(message, **options) ⇒ Object

Write a message to the connection.



213
214
215
216
217
218
219
220
221
222
223
224
# File 'lib/protocol/websocket/connection.rb', line 213

def write(message, **options)
	# This is a compatibility shim for the previous implementation. We may want to eventually deprecate this use case... or maybe it's convenient enough to leave it around.
	if message.is_a?(String)
		if message.encoding == Encoding::UTF_8
			return send_text(message, **options)
		else
			return send_binary(message, **options)
		end
	end
	
	message.send(self, **options)
end

#write_frame(frame) ⇒ Object



112
113
114
115
116
# File 'lib/protocol/websocket/connection.rb', line 112

def write_frame(frame)
	@framer.write_frame(frame)
	
	return frame
end