CrackedRuby CrackedRuby

Overview

WebSocket APIs provide a standardized way to establish persistent, bidirectional communication channels between clients and servers. Unlike HTTP's request-response model, WebSocket connections remain open after the initial handshake, allowing both parties to send data at any time without the overhead of repeated connection establishment.

The WebSocket protocol emerged from the need for real-time web applications that could push data to clients without polling. HTTP long-polling and server-sent events offered partial solutions, but introduced latency and complexity. WebSocket addresses these limitations by maintaining a single TCP connection upgraded from an initial HTTP handshake.

The protocol operates in two phases. First, the client sends an HTTP Upgrade request with specific headers indicating WebSocket support. If the server accepts, it responds with status 101 Switching Protocols, and the connection transitions to WebSocket mode. Second, the connection enters a message-passing phase where both sides exchange framed data packets. Each frame contains metadata about message type, length, and whether the message spans multiple frames.

# WebSocket handshake request headers
GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13

# Server response
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=

WebSocket connections support text frames (UTF-8 encoded) and binary frames for arbitrary data. Control frames handle connection management: ping frames test connection liveness, pong frames respond to pings, and close frames initiate graceful shutdown. The protocol includes fragmentation for splitting large messages across multiple frames and masking requirements for client-to-server messages to prevent cache poisoning attacks.

Key Principles

The WebSocket protocol defines a message-oriented communication layer over TCP. After the initial HTTP upgrade, all communication occurs through discrete messages that preserve boundaries. A message consists of one or more frames, where the final frame has the FIN bit set to indicate completion. This framing mechanism enables streaming large payloads without requiring the sender to know the total size upfront.

Frame structure includes an opcode field that identifies the frame type: continuation (0x0), text (0x1), binary (0x2), close (0x8), ping (0x9), or pong (0xA). The payload length occupies 7 bits for messages up to 125 bytes, with extended encoding for larger payloads. Client-originated frames include a 32-bit masking key applied to the payload data through XOR operation, while server-originated frames remain unmasked.

Connection lifecycle management follows a state machine. Connections begin in CONNECTING state during the handshake, transition to OPEN when established, enter CLOSING when either party sends a close frame, and finally reach CLOSED when the TCP connection terminates. Both parties must handle unexpected disconnections, which can occur due to network issues, timeouts, or crashes without clean shutdown.

The protocol requires specific close codes to indicate termination reasons. Code 1000 signals normal closure, 1001 indicates an endpoint is going away, 1002 reports protocol errors, 1003 rejects unacceptable data types, 1008 detects policy violations, and 1009 flags messages too large to process. Close frames can include a UTF-8 encoded reason string for debugging purposes.

Message ordering guarantees that frames sent by one endpoint arrive at the other endpoint in the same order, but messages from different connections have no ordering relationship. The protocol does not provide delivery confirmation or automatic retransmission—these concerns belong to the application layer.

Subprotocol negotiation allows clients and servers to agree on application-level protocols. The client lists acceptable subprotocols in the Sec-WebSocket-Protocol header, and the server selects one (or none) in its response. This mechanism enables multiple applications to share the same WebSocket infrastructure.

# Subprotocol negotiation example
# Client request
Sec-WebSocket-Protocol: chat, superchat

# Server response
Sec-WebSocket-Protocol: chat

Extension negotiation operates similarly through Sec-WebSocket-Extensions headers. Extensions modify frame structure or semantics, such as permessage-deflate for compression. Both parties must support an extension for it to activate.

The protocol distinguishes between connection-level and message-level concerns. Connection failures trigger immediate CLOSED state, while message-level errors (like receiving text data in a binary-only context) should be handled through close frames with appropriate codes. This separation allows graceful degradation when possible and immediate termination when the connection becomes unusable.

Ruby Implementation

Ruby provides multiple WebSocket implementations at different abstraction levels. The websocket-driver gem implements the low-level protocol specification, handling frame parsing, masking, and state management. Higher-level libraries like faye-websocket add event-driven APIs, while frameworks like ActionCable integrate WebSockets into Rails applications.

The websocket-driver gem exposes the protocol implementation separately from I/O operations. Applications create a driver instance, feed it data from the network, and consume its output to send over the connection. This design works with any I/O framework, whether EventMachine, nio4r, or blocking sockets.

require 'websocket/driver'

# Server-side driver
driver = WebSocket::Driver.server(socket)

driver.on :connect do |event|
  # Handshake successful
  driver.start
end

driver.on :message do |event|
  # event.data contains the message payload
  puts "Received: #{event.data}"
  driver.text("Echo: #{event.data}")
end

driver.on :close do |event|
  # event.code and event.reason contain close details
  puts "Closed: #{event.code} - #{event.reason}"
end

# Feed incoming data to driver
driver.parse(data_from_socket)

# Send driver output to socket
socket.write(driver.bytes_remaining)

The faye-websocket gem builds on websocket-driver with a simpler API suitable for common use cases. It handles I/O internally and provides EventMachine or asynchronous I/O support. The API mimics browser WebSocket objects.

require 'faye/websocket'
require 'eventmachine'

EM.run do
  # Client connection
  ws = Faye::WebSocket::Client.new('ws://echo.websocket.org/')

  ws.on :open do |event|
    ws.send('Hello, world!')
  end

  ws.on :message do |event|
    puts event.data
  end

  ws.on :error do |event|
    puts "Error: #{event.message}"
  end

  ws.on :close do |event|
    puts "Closed: #{event.code} - #{event.reason}"
    EM.stop
  end
end

ActionCable integrates WebSocket support into Rails through channels—Ruby classes that handle subscriptions and broadcasting. Channels abstract connection management, routing messages to appropriate handlers based on identifiers. ActionCable supports multiple connection backends including Redis and PostgreSQL for horizontal scaling.

# app/channels/chat_channel.rb
class ChatChannel < ApplicationCable::Channel
  def subscribed
    stream_from "chat_#{params[:room_id]}"
  end

  def receive(data)
    # Handle incoming message
    ActionCable.server.broadcast(
      "chat_#{params[:room_id]}",
      message: data['message'],
      user: current_user.name
    )
  end

  def unsubscribed
    # Cleanup when channel is unsubscribed
  end
end

Connection lifecycle requires careful resource management. WebSocket objects maintain buffers for partial frames and may hold references to large messages. Applications should implement timeouts for idle connections and limits on message sizes to prevent resource exhaustion.

require 'socket'
require 'websocket/driver'

class WebSocketServer
  def initialize(port)
    @server = TCPServer.new(port)
    @clients = {}
  end

  def run
    loop do
      socket = @server.accept
      Thread.new { handle_client(socket) }
    end
  end

  def handle_client(socket)
    driver = WebSocket::Driver.server(socket)
    @clients[socket] = driver

    driver.on :connect do
      driver.start
    end

    driver.on :message do |event|
      broadcast(event.data, exclude: socket)
    end

    driver.on :close do
      @clients.delete(socket)
      socket.close
    end

    loop do
      data = socket.readpartial(4096)
      driver.parse(data)
    end
  rescue EOFError, Errno::ECONNRESET
    @clients.delete(socket)
    socket.close
  end

  def broadcast(message, exclude: nil)
    @clients.each do |sock, driver|
      next if sock == exclude
      driver.text(message)
      sock.write(driver.bytes_remaining)
    end
  end
end

Message serialization requires explicit handling. WebSocket frames contain raw bytes (for binary frames) or UTF-8 strings (for text frames). Applications typically use JSON for text messages or protocol buffers for binary messages.

require 'json'

# Sending structured data
data = { type: 'chat', message: 'Hello', timestamp: Time.now.to_i }
driver.text(JSON.generate(data))

# Receiving structured data
driver.on :message do |event|
  data = JSON.parse(event.data)
  handle_message(data['type'], data)
rescue JSON::ParserError => e
  driver.close(1003, "Invalid JSON")
end

Ruby's Thread model affects WebSocket implementations. Each connection typically requires a dedicated thread when using blocking I/O, limiting scalability. EventMachine or Async provide non-blocking alternatives that multiplex thousands of connections per process. ActionCable uses Concurrent Ruby for thread management.

Practical Examples

A basic echo server demonstrates WebSocket fundamentals. The server accepts connections, echoes back any received messages, and handles connection lifecycle properly.

require 'socket'
require 'websocket/driver'
require 'base64'
require 'digest/sha1'

class EchoServer
  def initialize(port = 8080)
    @server = TCPServer.new(port)
  end

  def start
    puts "WebSocket echo server listening on port #{@server.addr[1]}"
    
    loop do
      socket = @server.accept
      Thread.new { handle_connection(socket) }
    end
  end

  private

  def handle_connection(socket)
    # Perform HTTP to WebSocket upgrade
    request = socket.gets
    headers = {}
    
    while (line = socket.gets.chomp) != ""
      key, value = line.split(": ", 2)
      headers[key] = value
    end

    if headers["Upgrade"] == "websocket"
      perform_handshake(socket, headers)
      handle_websocket(socket)
    else
      socket.close
    end
  rescue => e
    puts "Error: #{e.message}"
    socket.close
  end

  def perform_handshake(socket, headers)
    key = headers["Sec-WebSocket-Key"]
    accept = Base64.strict_encode64(
      Digest::SHA1.digest(key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11")
    )

    response = [
      "HTTP/1.1 101 Switching Protocols",
      "Upgrade: websocket",
      "Connection: Upgrade",
      "Sec-WebSocket-Accept: #{accept}",
      "\r\n"
    ].join("\r\n")

    socket.write(response)
  end

  def handle_websocket(socket)
    driver = WebSocket::Driver.server(socket)
    
    driver.on :connect do
      driver.start
    end

    driver.on :message do |event|
      puts "Received: #{event.data}"
      driver.text("Echo: #{event.data}")
      socket.write(driver.bytes_remaining)
    end

    driver.on :close do |event|
      puts "Connection closed: #{event.code}"
      socket.close
    end

    loop do
      data = socket.readpartial(4096)
      driver.parse(data)
    end
  rescue EOFError, Errno::ECONNRESET
    socket.close
  end
end

EchoServer.new.start

A chat room application shows message broadcasting across multiple connections. The server maintains a list of active connections and distributes messages to all participants.

require 'faye/websocket'
require 'eventmachine'
require 'json'

class ChatRoom
  def initialize
    @clients = []
  end

  def add_client(ws)
    @clients << ws
    broadcast({ type: 'join', count: @clients.length })
  end

  def remove_client(ws)
    @clients.delete(ws)
    broadcast({ type: 'leave', count: @clients.length })
  end

  def broadcast(message, exclude: nil)
    data = message.is_a?(String) ? message : JSON.generate(message)
    
    @clients.each do |client|
      next if client == exclude
      client.send(data)
    end
  end

  def handle_message(ws, data)
    message = JSON.parse(data)
    
    case message['type']
    when 'chat'
      broadcast({
        type: 'chat',
        user: message['user'],
        text: message['text'],
        timestamp: Time.now.to_i
      }, exclude: ws)
    when 'typing'
      broadcast({
        type: 'typing',
        user: message['user']
      }, exclude: ws)
    end
  rescue JSON::ParserError
    ws.send(JSON.generate({ type: 'error', message: 'Invalid JSON' }))
  end
end

EM.run do
  room = ChatRoom.new

  EM::WebSocket.run(host: '0.0.0.0', port: 8080) do |ws|
    ws.onopen do
      room.add_client(ws)
    end

    ws.onmessage do |message|
      room.handle_message(ws, message)
    end

    ws.onclose do
      room.remove_client(ws)
    end

    ws.onerror do |error|
      puts "WebSocket error: #{error.message}"
    end
  end

  puts "Chat room running on ws://localhost:8080"
end

Real-time data streaming requires handling backpressure when clients consume data slower than production rate. The server must either buffer messages, drop old data, or throttle producers.

class StreamingConnection
  MAX_BUFFER_SIZE = 1000
  
  def initialize(driver, socket)
    @driver = driver
    @socket = socket
    @buffer = []
    @flushing = false
  end

  def send_data(data)
    @buffer << data
    
    if @buffer.length > MAX_BUFFER_SIZE
      # Drop oldest messages to prevent memory exhaustion
      @buffer.shift
    end
    
    flush unless @flushing
  end

  private

  def flush
    return if @buffer.empty? || @flushing
    
    @flushing = true
    
    while message = @buffer.shift
      @driver.text(JSON.generate(message))
      
      begin
        @socket.write_nonblock(@driver.bytes_remaining)
      rescue IO::WaitWritable
        # Socket buffer full, re-queue message
        @buffer.unshift(message)
        
        # Schedule retry after socket becomes writable
        IO.select(nil, [@socket], nil, 0.1)
        retry
      end
    end
    
    @flushing = false
  end
end

WebSocket clients must implement reconnection logic to handle temporary network failures. Exponential backoff prevents overwhelming the server during outages.

class ResilientWebSocketClient
  MAX_RECONNECT_DELAY = 30
  
  def initialize(url)
    @url = url
    @reconnect_delay = 1
    @should_reconnect = true
  end

  def connect
    @ws = Faye::WebSocket::Client.new(@url)
    
    @ws.on :open do
      puts "Connected"
      @reconnect_delay = 1
      on_open if respond_to?(:on_open)
    end

    @ws.on :message do |event|
      on_message(event.data) if respond_to?(:on_message)
    end

    @ws.on :close do |event|
      puts "Disconnected: #{event.code}"
      schedule_reconnect if @should_reconnect
    end

    @ws.on :error do |event|
      puts "Error: #{event.message}"
    end
  end

  def disconnect
    @should_reconnect = false
    @ws.close if @ws
  end

  def send(data)
    @ws.send(data) if @ws
  end

  private

  def schedule_reconnect
    sleep @reconnect_delay
    @reconnect_delay = [@reconnect_delay * 2, MAX_RECONNECT_DELAY].min
    
    puts "Reconnecting..."
    connect
  end
end

Security Implications

WebSocket connections inherit the security context of the page that created them. Cross-origin connections require explicit server authorization through the Sec-WebSocket-Protocol header check and origin validation during handshake. Servers must verify the Origin header matches expected values to prevent unauthorized domains from establishing connections.

def validate_origin(headers)
  origin = headers['Origin']
  allowed_origins = ['https://example.com', 'https://app.example.com']
  
  unless allowed_origins.include?(origin)
    return [403, { 'Content-Type' => 'text/plain' }, ['Forbidden']]
  end
  
  # Continue with handshake
end

Authentication must occur before or during the WebSocket handshake since the connection remains open afterward. Common approaches include passing authentication tokens in URL query parameters, using cookies from the initial HTTP request, or sending credentials in the first WebSocket message.

# ActionCable authentication example
module ApplicationCable
  class Connection < ActionCable::Connection::Base
    identified_by :current_user

    def connect
      self.current_user = find_verified_user
    end

    private

    def find_verified_user
      token = request.params[:token]
      
      if user = User.find_by(auth_token: token)
        user
      else
        reject_unauthorized_connection
      end
    end
  end
end

WebSocket messages are not automatically sanitized. Applications must validate and escape all message content before processing or broadcasting to prevent XSS attacks. Text frames should be treated as untrusted input regardless of source.

require 'cgi'

def handle_chat_message(data)
  message = JSON.parse(data)
  
  # Validate message structure
  unless message['text'].is_a?(String) && message['text'].length <= 1000
    return { error: 'Invalid message' }
  end
  
  # Sanitize HTML content
  sanitized_text = CGI.escapeHTML(message['text'])
  
  broadcast({
    type: 'chat',
    text: sanitized_text,
    user: current_user.name
  })
end

Rate limiting prevents abuse from malicious or misbehaving clients. Servers should track message frequency per connection and close connections that exceed thresholds.

class RateLimitedConnection
  MAX_MESSAGES_PER_MINUTE = 60
  
  def initialize
    @message_times = []
  end

  def check_rate_limit
    now = Time.now
    
    # Remove messages older than 1 minute
    @message_times.reject! { |time| now - time > 60 }
    
    if @message_times.length >= MAX_MESSAGES_PER_MINUTE
      return false
    end
    
    @message_times << now
    true
  end

  def handle_message(data)
    unless check_rate_limit
      close(1008, "Rate limit exceeded")
      return
    end
    
    process_message(data)
  end
end

WebSocket connections do not enforce same-origin policy for subresources. Messages can contain URLs to arbitrary resources, requiring careful validation before fetching referenced content. Applications should whitelist allowed domains or protocols.

TLS encryption (wss:// protocol) protects message content in transit. WebSocket connections established from HTTPS pages must use WSS to prevent mixed content warnings and maintain security guarantees. Certificate validation applies identically to HTTPS.

Compression through the permessage-deflate extension introduces security considerations. CRIME and BREACH attacks exploit compression side channels by observing compressed message sizes. Applications transmitting sensitive data alongside user-controlled content should disable compression or implement countermeasures like message padding.

# Disable compression for sensitive connections
driver = WebSocket::Driver.server(socket)
driver.add_extension(
  WebSocket::Driver::Extension.new('permessage-deflate')
) unless sensitive_connection?

Connection hijacking can occur if an attacker obtains session credentials or cookies. WebSocket connections should implement additional authorization checks for sensitive operations rather than relying solely on connection authentication.

Denial of service attacks target connection resources or message processing. Servers must limit per-connection message sizes, overall connection counts, and CPU time spent processing messages. Memory exhaustion from large queues or fragmented messages requires explicit safeguards.

Performance Considerations

WebSocket connections maintain TCP state and application-level buffers, consuming more resources than stateless HTTP requests. Each connection requires file descriptors, memory for partial frames, and thread or event loop space. Servers typically handle 10,000 to 100,000 concurrent connections per process depending on architecture.

Message framing overhead varies by payload size. Short messages incur 2-6 bytes per frame for header information, making WebSockets less efficient than HTTP for tiny payloads sent infrequently. The protocol excels when sending frequent messages or maintaining bidirectional communication channels.

# Benchmark comparing HTTP polling vs WebSocket
require 'benchmark'

# HTTP polling: 1000 requests
http_time = Benchmark.measure do
  1000.times do
    # Each request requires full TCP handshake + HTTP overhead
    # ~500-1000 bytes overhead per request
  end
end

# WebSocket: 1000 messages over single connection
ws_time = Benchmark.measure do
  # Single TCP connection + WebSocket handshake
  # ~4-6 bytes overhead per message
  1000.times { ws.send(small_message) }
end
# WebSocket is 10-50x faster for frequent small messages

Frame masking applied to client messages requires XOR operations on every byte, adding CPU overhead. Servers send unmasked frames to reduce processing requirements. For bulk data transfer, binary frames avoid UTF-8 validation costs present in text frames.

Fragmentation allows streaming large messages without buffering the entire payload, reducing memory consumption. Applications can begin processing data as fragments arrive rather than waiting for message completion.

def stream_large_file(driver, socket, file_path)
  File.open(file_path, 'rb') do |file|
    first_chunk = true
    
    while chunk = file.read(4096)
      is_final = file.eof?
      
      if first_chunk
        driver.binary(chunk, final: is_final)
        first_chunk = false
      else
        driver.binary(chunk, final: is_final)
      end
      
      socket.write(driver.bytes_remaining)
    end
  end
end

The permessage-deflate extension compresses individual messages using DEFLATE. Compression ratios vary by data type—JSON and text compress well, while binary protocols may see minimal benefit. Compression adds CPU overhead during frame processing and increases latency for small messages.

# Configure compression parameters
driver = WebSocket::Driver.server(socket)
driver.add_extension(
  WebSocket::Driver::Extension.new('permessage-deflate', {
    server_max_window_bits: 15,  # Memory vs compression tradeoff
    client_max_window_bits: 15,
    server_no_context_takeover: true  # Don't persist state between messages
  })
)

Connection pooling on clients reduces handshake overhead when creating multiple logical channels to the same server. A single WebSocket connection can multiplex multiple application-level streams through message routing.

Network conditions affect WebSocket performance more than HTTP due to connection persistence. Packet loss triggers TCP retransmission, blocking all messages until recovery completes. Mobile networks with frequent handoffs may cause repeated reconnections.

Heartbeat messages keep connections alive through intermediate proxies and firewalls that timeout idle connections. Ping/pong frames provide protocol-level keepalive, while application messages serve the same purpose for proxies that don't parse WebSocket frames.

def start_heartbeat(driver, socket, interval: 30)
  Thread.new do
    loop do
      sleep interval
      driver.ping('keepalive')
      socket.write(driver.bytes_remaining)
    end
  end
end

Horizontal scaling requires session affinity (sticky connections) or shared state across servers. Load balancers must route all messages for a connection to the same backend. Redis or similar stores enable message broadcasting across multiple server processes.

# ActionCable with Redis adapter for scaling
# config/cable.yml
production:
  adapter: redis
  url: redis://localhost:6379/1
  channel_prefix: app_production

# Messages broadcast through Redis reach all server processes
ActionCable.server.broadcast('channel', message: 'data')

Common Pitfalls

WebSocket connection state requires careful tracking. Applications often fail to handle the CLOSING state correctly, attempting to send messages after receiving a close frame but before the TCP connection terminates. Sends during CLOSING state should be silently ignored or queued for later reconnection.

class StatefulConnection
  def initialize
    @state = :connecting
  end

  def send_message(data)
    case @state
    when :open
      @driver.text(data)
    when :closing, :closed
      # Ignore or queue message
      puts "Cannot send on closed connection"
    end
  end

  def handle_close
    @state = :closing
    # Flush pending messages before closing
    flush_outbound_queue
    @state = :closed
  end
end

Forgetting to handle fragmented messages leads to data corruption. Applications must buffer incoming fragments until receiving a frame with the FIN bit set. The websocket-driver gem handles this automatically, but low-level implementations require explicit fragment management.

Insufficient error handling for malformed frames causes protocol violations. The WebSocket spec requires closing the connection with appropriate status codes when detecting protocol errors. Applications should never attempt to continue using a connection after detecting frame corruption.

Assuming message delivery works identically to HTTP creates reliability issues. WebSocket provides ordered delivery but no automatic retransmission or acknowledgment. Applications needing delivery guarantees must implement application-level acknowledgment protocols.

class ReliableMessaging
  def initialize
    @pending_messages = {}
    @next_id = 0
  end

  def send_with_ack(data)
    message_id = @next_id
    @next_id += 1
    
    @pending_messages[message_id] = {
      data: data,
      sent_at: Time.now,
      retry_count: 0
    }
    
    send_message({ id: message_id, type: 'data', payload: data })
  end

  def handle_ack(message_id)
    @pending_messages.delete(message_id)
  end

  def check_timeouts
    @pending_messages.each do |id, message|
      if Time.now - message[:sent_at] > 5
        if message[:retry_count] < 3
          message[:retry_count] += 1
          message[:sent_at] = Time.now
          send_message({ id: id, type: 'data', payload: message[:data] })
        else
          # Give up after 3 retries
          handle_send_failure(id)
        end
      end
    end
  end
end

Memory leaks occur when connection objects maintain references to closed connections or accumulate message history. Event handlers should be explicitly removed when connections close, and message buffers should have size limits.

Blocking operations in message handlers prevent processing subsequent messages. EventMachine and similar frameworks require non-blocking operations or offloading work to background threads.

# Bad: blocks event loop
ws.on :message do |event|
  # Blocking database query
  result = Database.query("SELECT * FROM large_table")
  ws.send(result)
end

# Good: offload to thread pool
ws.on :message do |event|
  Thread.pool.process do
    result = Database.query("SELECT * FROM large_table")
    ws.send(result)
  end
end

URL encoding issues arise when passing authentication tokens or parameters in the WebSocket URL. Standard URL encoding rules apply, but many implementations incorrectly handle special characters.

Race conditions occur during connection establishment when applications send messages before the open event fires. Message sends before connection establishment should be queued and transmitted after the handshake completes.

Proxy and firewall incompatibilities cause mysterious connection failures. Some middleboxes do not recognize the WebSocket upgrade or timeout connections based on inactivity. Applications should implement connection retry logic and consider fallback transports.

Reference

WebSocket States

State Description Valid Operations
CONNECTING Initial handshake in progress None, wait for OPEN
OPEN Connection established, ready for messages send, close, ping
CLOSING Close frame sent or received, waiting for closure None, connection terminating
CLOSED Connection terminated None, create new connection

Frame Types

Opcode Type Description Control Frame
0x0 Continuation Continuation of fragmented message No
0x1 Text UTF-8 text data No
0x2 Binary Arbitrary binary data No
0x8 Close Connection close request Yes
0x9 Ping Connection liveness check Yes
0xA Pong Response to ping Yes

Close Status Codes

Code Name Meaning
1000 Normal Closure Successful operation complete
1001 Going Away Endpoint unavailable (page unload, server shutdown)
1002 Protocol Error Protocol violation detected
1003 Unsupported Data Data type not acceptable
1006 Abnormal Closure Connection lost without close frame
1007 Invalid Payload Message data inconsistent with type
1008 Policy Violation Message violates policy
1009 Message Too Big Message exceeds size limit
1011 Server Error Server encountered error

Ruby WebSocket Libraries

Library Level Use Case
websocket-driver Low-level Protocol implementation, custom I/O
faye-websocket Mid-level EventMachine integration, client/server
ActionCable High-level Rails integration, channels, broadcasting
websocket-ruby Mid-level Synchronous I/O, simple applications
em-websocket Mid-level EventMachine server implementation

Connection Handshake Headers

Header Direction Purpose
Upgrade: websocket Request Requests protocol upgrade
Connection: Upgrade Request Indicates upgrade request
Sec-WebSocket-Key Request Random value for handshake
Sec-WebSocket-Version Request WebSocket protocol version
Sec-WebSocket-Protocol Both Subprotocol negotiation
Sec-WebSocket-Extensions Both Extension negotiation
Sec-WebSocket-Accept Response Handshake validation hash

Message Size Limits

Implementation Default Max Size Configurable
Browser clients 2GB theoretical Implementation dependent
websocket-driver No default limit Application enforced
ActionCable 256KB Yes, cable.yml max_message_size
Nginx proxy 1MB client_max_body_size

Performance Characteristics

Operation Overhead Notes
Initial handshake 1 RTT + TLS Same as HTTPS request
Message frame 2-14 bytes Depends on payload length encoding
Client masking 4 bytes + XOR per byte Required for client-to-server
Ping/Pong 2 bytes Control frame overhead
Close handshake 1 RTT Clean connection termination

Common Configuration Parameters

Parameter Typical Value Purpose
Max connections per process 10000-100000 Resource limits
Message size limit 1-10 MB Prevent memory exhaustion
Idle timeout 60-300 seconds Detect dead connections
Ping interval 30-60 seconds Keep connection alive
Reconnect backoff 1-30 seconds Exponential backoff max
Message queue depth 100-1000 Backpressure handling