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.



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

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

Instance Attribute Details

#Buffered frames which form part of a complete message.(frameswhichformpartofacompletemessage.) ⇒ Object (readonly)



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

attr_accessor :frames

#framerObject (readonly)

Returns the value of attribute framer.



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

def framer
  @framer
end

#framesObject

Returns the value of attribute frames.



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

def frames
  @frames
end

#maskObject (readonly)

Returns the value of attribute mask.



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

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)

Returns the value of attribute reserved.



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

def reserved
  @reserved
end

#The allowed reserved bits.(allowedreservedbits.) ⇒ Object (readonly)



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

attr :reserved

#The framer which is used for reading and writing frames.(framerwhichisused) ⇒ Object (readonly)



29
# File 'lib/protocol/websocket/connection.rb', line 29

attr :framer

#The optional mask which is used when generating frames.(optionalmaskwhichisused) ⇒ Object (readonly)



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

attr :mask

#writerObject

Returns the value of attribute writer.



44
45
46
# File 'lib/protocol/websocket/connection.rb', line 44

def writer
  @writer
end

Instance Method Details

#closeObject

Immediately transition the connection to the closed state and close the underlying connection. Any data not yet read will be lost.



92
93
94
95
96
# File 'lib/protocol/websocket/connection.rb', line 92

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

#close!Object

If not already closed, transition the connection to the closed state and send a close frame. Will try to send a close frame with the specified code and reason, but will ignore any errors that occur while sending.



72
73
74
75
76
77
78
79
80
81
82
83
84
# File 'lib/protocol/websocket/connection.rb', line 72

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

#close_write(error = nil) ⇒ Object

Close the connection gracefully, sending a close frame with the specified error code and reason. If an error occurs while sending the close frame, the connection will be closed immediately. You may continue to read data from the connection after calling this method, but you should not write any more data.



101
102
103
104
105
106
107
# File 'lib/protocol/websocket/connection.rb', line 101

def close_write(error = nil)
	if error
		send_close(Error::INTERNAL_ERROR, error.message)
	else
		send_close
	end
end

#closed?Boolean

Returns:

  • (Boolean)


87
88
89
# File 'lib/protocol/websocket/connection.rb', line 87

def closed?
	@state == :closed
end

#flushObject

Flush the underlying framer to ensure all buffered data is written to the connection.



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

def flush
	@framer.flush
end

#open!Object

Transition the connection to the open state (the default for new connections).



64
65
66
67
68
# File 'lib/protocol/websocket/connection.rb', line 64

def open!
	@state = :open
	
	return self
end

#pack_binary_frame(buffer, **options) ⇒ Object

Pack a binary frame with the specified buffer. This is used by the #writer interface.



240
241
242
243
244
245
# File 'lib/protocol/websocket/connection.rb', line 240

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

#pack_text_frame(buffer, **options) ⇒ Object

Pack a text frame with the specified buffer. This is used by the #writer interface.



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

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. If an error occurs while reading the message, the connection will be closed.

If the message is fragmented, this method will buffer the frames until a complete message is received.



292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
# File 'lib/protocol/websocket/connection.rb', line 292

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

Read a frame from the framer, and apply it to the connection.



120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
# File 'lib/protocol/websocket/connection.rb', line 120

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

Receive a binary frame for the connection.



160
161
162
163
164
165
166
# File 'lib/protocol/websocket/connection.rb', line 160

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

#receive_close(frame) ⇒ Object

Receive a close frame from the connection.



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

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

Receive a continuation frame for the connection.



169
170
171
172
173
174
175
# File 'lib/protocol/websocket/connection.rb', line 169

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

#receive_frame(frame) ⇒ Object

Receive a frame that is not a control frame. By default, this method raises a ProtocolError.

Raises:



217
218
219
# File 'lib/protocol/websocket/connection.rb', line 217

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

#receive_ping(frame) ⇒ Object

Receive a ping frame from the connection.



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

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

Receive a pong frame from the connection. By default, this method does nothing.



212
213
214
# File 'lib/protocol/websocket/connection.rb', line 212

def receive_pong(frame)
	# Ignore.
end

#receive_text(frame) ⇒ Object

Receive a text frame from the connection.



151
152
153
154
155
156
157
# File 'lib/protocol/websocket/connection.rb', line 151

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

#reserve!(bit) ⇒ Object

Reserve a bit in the reserved flags for an extension.



48
49
50
51
52
53
54
55
56
# File 'lib/protocol/websocket/connection.rb', line 48

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

#send_binary(buffer, **options) ⇒ Object

Send a binary frame with the specified buffer.



249
250
251
# File 'lib/protocol/websocket/connection.rb', line 249

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.



256
257
258
259
260
261
262
# File 'lib/protocol/websocket/connection.rb', line 256

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

Send a ping frame with the specified data.



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

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

Send a text frame with the specified buffer.



233
234
235
# File 'lib/protocol/websocket/connection.rb', line 233

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

#shutdownObject

Close the connection gracefully. This will send a close frame and wait for the remote end to respond with a close frame. Any data received after the close frame is sent will be ignored. If you want to process this data, use #close_write instead, and read the data before calling #close.



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

def shutdown
	send_close unless @state == :closed
	
	# `read_frame` will return nil after receiving a close frame:
	while read_frame
		# Drain the connection.
	end
end

#The reader which is used to unpack frames into messages.=(readerwhichisusedtounpackframesintomessages. = (value)) ⇒ Object



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

attr_accessor :reader

#The writer which is used to pack messages into frames.=(writerwhichisusedtopackmessagesintoframes. = (value)) ⇒ Object



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

attr_accessor :writer

#unpack_frames(frames) ⇒ Object

The default implementation for reading a message buffer. This is used by the #reader interface.



283
284
285
# File 'lib/protocol/websocket/connection.rb', line 283

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

#write(message, **options) ⇒ Object

Write a message to the connection.



266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
# File 'lib/protocol/websocket/connection.rb', line 266

def write(message, **options)
	case message
	when String
		# 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.encoding == Encoding::UTF_8
			return send_text(message, **options)
		else
			return send_binary(message, **options)
		end
	when Message
		message.send(self, **options)
	else
		raise ArgumentError, "Unsupported message type: #{message}"
	end
end

#write_frame(frame) ⇒ Object

Write a frame to the framer. Note: This does not immediately write the frame to the connection, you must call #flush to ensure the frame is written.



144
145
146
147
148
# File 'lib/protocol/websocket/connection.rb', line 144

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