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
108
109
# 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
rescue
	@state = :closed
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.



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

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.



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

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.



298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
# File 'lib/protocol/websocket/connection.rb', line 298

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.



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

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.



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

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.



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

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.



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

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:



219
220
221
# File 'lib/protocol/websocket/connection.rb', line 219

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

#receive_ping(frame) ⇒ Object

Receive a ping frame from the connection.



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

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.



214
215
216
# File 'lib/protocol/websocket/connection.rb', line 214

def receive_pong(frame)
	# Ignore.
end

#receive_text(frame) ⇒ Object

Receive a text frame from the connection.



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

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.



251
252
253
# File 'lib/protocol/websocket/connection.rb', line 251

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.



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

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.



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

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.



235
236
237
# File 'lib/protocol/websocket/connection.rb', line 235

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.



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

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.



285
286
287
288
289
290
291
# File 'lib/protocol/websocket/connection.rb', line 285

def unpack_frames(frames)
	if frames.size == 1
		frames[0].unpack
	else
		frames.map(&:unpack).join("")
	end
end

#write(message, **options) ⇒ Object

Write a message to the connection.



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

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.



146
147
148
149
150
# File 'lib/protocol/websocket/connection.rb', line 146

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