Class: Protocol::WebSocket::Connection
- Inherits:
-
Object
- Object
- Protocol::WebSocket::Connection
- 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
- #Buffered frames which form part of a complete message.(frameswhichformpartofacompletemessage.) ⇒ Object readonly
-
#framer ⇒ Object
readonly
Returns the value of attribute framer.
-
#frames ⇒ Object
Returns the value of attribute frames.
-
#mask ⇒ Object
readonly
Returns the value of attribute mask.
-
#reader ⇒ Object
Returns the value of attribute reader.
-
#reserved ⇒ Object
readonly
Returns the value of attribute reserved.
- #The allowed reserved bits.(allowedreservedbits.) ⇒ Object readonly
- #The framer which is used for reading and writing frames.(framerwhichisused) ⇒ Object readonly
- #The optional mask which is used when generating frames.(optionalmaskwhichisused) ⇒ Object readonly
-
#writer ⇒ Object
Returns the value of attribute writer.
Instance Method Summary collapse
-
#close ⇒ Object
Immediately transition the connection to the closed state and close the underlying connection.
-
#close! ⇒ Object
If not already closed, transition the connection to the closed state and send a close frame.
-
#close_write(error = nil) ⇒ Object
Close the connection gracefully, sending a close frame with the specified error code and reason.
- #closed? ⇒ Boolean
-
#flush ⇒ Object
Flush the underlying framer to ensure all buffered data is written to the connection.
-
#initialize(framer, mask: nil, **options) ⇒ Connection
constructor
A new instance of Connection.
-
#open! ⇒ Object
Transition the connection to the open state (the default for new connections).
-
#pack_binary_frame(buffer, **options) ⇒ Object
Pack a binary frame with the specified buffer.
-
#pack_text_frame(buffer, **options) ⇒ Object
Pack a text frame with the specified buffer.
-
#read(**options) ⇒ Object
Read a message from the connection.
-
#read_frame ⇒ Object
Read a frame from the framer, and apply it to the connection.
-
#receive_binary(frame) ⇒ Object
Receive a binary frame for the connection.
-
#receive_close(frame) ⇒ Object
Receive a close frame from the connection.
-
#receive_continuation(frame) ⇒ Object
Receive a continuation frame for the connection.
-
#receive_frame(frame) ⇒ Object
Receive a frame that is not a control frame.
-
#receive_ping(frame) ⇒ Object
Receive a ping frame from the connection.
-
#receive_pong(frame) ⇒ Object
Receive a pong frame from the connection.
-
#receive_text(frame) ⇒ Object
Receive a text frame from the connection.
-
#reserve!(bit) ⇒ Object
Reserve a bit in the reserved flags for an extension.
-
#send_binary(buffer, **options) ⇒ Object
Send a binary frame with the specified buffer.
-
#send_close(code = Error::NO_ERROR, reason = "") ⇒ Object
Send a control frame with data containing a specified control sequence to begin the closing handshake.
-
#send_ping(data = "") ⇒ Object
Send a ping frame with the specified data.
-
#send_text(buffer, **options) ⇒ Object
Send a text frame with the specified buffer.
-
#shutdown ⇒ Object
Close the connection gracefully.
- #The reader which is used to unpack frames into messages.=(readerwhichisusedtounpackframesintomessages. = (value)) ⇒ Object
- #The writer which is used to pack messages into frames.=(writerwhichisusedtopackmessagesintoframes. = (value)) ⇒ Object
-
#unpack_frames(frames) ⇒ Object
The default implementation for reading a message buffer.
-
#write(message, **options) ⇒ Object
Write a message to the connection.
-
#write_frame(frame) ⇒ Object
Write a frame to the framer.
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, **) @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 |
#framer ⇒ Object (readonly)
Returns the value of attribute framer.
29 30 31 |
# File 'lib/protocol/websocket/connection.rb', line 29 def framer @framer end |
#frames ⇒ Object
Returns the value of attribute frames.
38 39 40 |
# File 'lib/protocol/websocket/connection.rb', line 38 def frames @frames end |
#mask ⇒ Object (readonly)
Returns the value of attribute mask.
32 33 34 |
# File 'lib/protocol/websocket/connection.rb', line 32 def mask @mask end |
#reader ⇒ Object
Returns the value of attribute reader.
41 42 43 |
# File 'lib/protocol/websocket/connection.rb', line 41 def reader @reader end |
#reserved ⇒ Object (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 |
#writer ⇒ Object
Returns the value of attribute writer.
44 45 46 |
# File 'lib/protocol/websocket/connection.rb', line 44 def writer @writer end |
Instance Method Details
#close ⇒ Object
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.) else send_close end end |
#closed? ⇒ Boolean
87 88 89 |
# File 'lib/protocol/websocket/connection.rb', line 87 def closed? @state == :closed end |
#flush ⇒ Object
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, **) 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, **) 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(**) @framer.flush while read_frame if @frames.last&.finished? frames = @frames @frames = [] buffer = @reader.unpack_frames(frames, **) return frames.first.(buffer) end end rescue ProtocolError => error close(error.code, error.) raise end |
#read_frame ⇒ Object
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.) raise rescue => error close(Error::PROTOCOL_ERROR, error.) 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.
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, **) write_frame(@writer.pack_binary_frame(buffer, **)) 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, **) write_frame(@writer.pack_text_frame(buffer, **)) end |
#shutdown ⇒ Object
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(, **) case 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 .encoding == Encoding::UTF_8 return send_text(, **) else return send_binary(, **) end when Message .send(self, **) else raise ArgumentError, "Unsupported message type: #{}" 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 |