123456789_123456789_123456789_123456789_123456789_

Class: ActionCable::Server::Socket

Overview

This class encapsulates all the low-level logic of working with the underlying WebSocket conenctions and delegate all the business-logic to the user-level connection object (e.g., ApplicationCable::Connection). This connection object is also responsible for handling encoding and decoding of messages, so the user-level connection object shouldn't know about such details.

Class Method Summary

Instance Attribute Summary

Instance Method Summary

Constructor Details

.new(server, env, coder: ActiveSupport::JSON) ⇒ Socket

[ GitHub ]

  
# File 'actioncable/lib/action_cable/server/socket.rb', line 18

def initialize(server, env, coder: ActiveSupport::JSON)
  @server, @env, @coder = server, env, coder

  @worker_pool = server.worker_pool
  @logger = server.new_tagged_logger { request }

  @websocket      = WebSocket.new(env, self, event_loop)
  @message_buffer = MessageBuffer.new(self)

  @protocol = nil
  @connection = config.connection_class.call.new(server, self)
end

Instance Attribute Details

#config (readonly)

[ GitHub ]

  
# File 'actioncable/lib/action_cable/server/socket.rb', line 16

delegate :event_loop, :pubsub, :config, to: :server

#connection (readonly)

[ GitHub ]

  
# File 'actioncable/lib/action_cable/server/socket.rb', line 13

attr_reader :server, :env, :protocol, :logger, :connection

#env (readonly)

[ GitHub ]

  
# File 'actioncable/lib/action_cable/server/socket.rb', line 13

attr_reader :server, :env, :protocol, :logger, :connection

#event_loop (readonly)

[ GitHub ]

  
# File 'actioncable/lib/action_cable/server/socket.rb', line 16

delegate :event_loop, :pubsub, :config, to: :server

#logger (readonly)

[ GitHub ]

  
# File 'actioncable/lib/action_cable/server/socket.rb', line 13

attr_reader :server, :env, :protocol, :logger, :connection

#message_buffer (readonly, private)

[ GitHub ]

  
# File 'actioncable/lib/action_cable/server/socket.rb', line 114

attr_reader :message_buffer

#protocol (readonly)

[ GitHub ]

  
# File 'actioncable/lib/action_cable/server/socket.rb', line 13

attr_reader :server, :env, :protocol, :logger, :connection

#pubsub (readonly)

[ GitHub ]

  
# File 'actioncable/lib/action_cable/server/socket.rb', line 16

delegate :event_loop, :pubsub, :config, to: :server

#server (readonly)

[ GitHub ]

  
# File 'actioncable/lib/action_cable/server/socket.rb', line 13

attr_reader :server, :env, :protocol, :logger, :connection

#websocket (readonly, private)

[ GitHub ]

  
# File 'actioncable/lib/action_cable/server/socket.rb', line 113

attr_reader :websocket

#worker_pool (readonly, private)

[ GitHub ]

  
# File 'actioncable/lib/action_cable/server/socket.rb', line 14

private attr_reader :worker_pool

Instance Method Details

#close

Close the Socket::WebSocket connection.

[ GitHub ]

  
# File 'actioncable/lib/action_cable/server/socket.rb', line 52

def close(...)
  websocket.close(...) if websocket.alive?
end

#decode(websocket_message) (private)

[ GitHub ]

  
# File 'actioncable/lib/action_cable/server/socket.rb', line 120

def decode(websocket_message)
  @coder.decode websocket_message
end

#dispatch_websocket_message(websocket_message)

This method is for internal use only.
[ GitHub ]

  
# File 'actioncable/lib/action_cable/server/socket.rb', line 79

def dispatch_websocket_message(websocket_message) # :nodoc:
  if websocket.alive?
    @connection.handle_incoming decode(websocket_message)
  else
    logger.error "Ignoring message processed after the WebSocket was closed: #{websocket_message.inspect})"
  end
rescue Exception => e
  logger.error "Could not handle incoming message: #{websocket_message.inspect} [#{e.class} - #{e.message}]: #{e.backtrace.first(5).join(" | ")}"
end

#encode(cable_message) (private)

[ GitHub ]

  
# File 'actioncable/lib/action_cable/server/socket.rb', line 116

def encode(cable_message)
  @coder.encode cable_message
end

#finished_request_message (private)

[ GitHub ]

  
# File 'actioncable/lib/action_cable/server/socket.rb', line 162

def finished_request_message
  'Finished "%s"%s for %s at %s' % [
    request.filtered_path,
    websocket.possible? ? " [WebSocket]" : "[non-WebSocket]",
    request.ip,
    Time.now.to_s ]
end

#handle_close (private)

[ GitHub ]

  
# File 'actioncable/lib/action_cable/server/socket.rb', line 133

def handle_close
  logger.info finished_request_message

  server.remove_connection(@connection)
  @connection.handle_close
end

#handle_open (private)

[ GitHub ]

  
# File 'actioncable/lib/action_cable/server/socket.rb', line 124

def handle_open
  @protocol = websocket.protocol

  @connection.handle_open

  message_buffer.process!
  server.add_connection(@connection)
end

#instance_variables_to_inspect (private)

[ GitHub ]

  
# File 'actioncable/lib/action_cable/server/socket.rb', line 109

def instance_variables_to_inspect
  [].freeze
end

#invalid_request_message (private)

[ GitHub ]

  
# File 'actioncable/lib/action_cable/server/socket.rb', line 170

def invalid_request_message
  "Failed to upgrade to WebSocket (REQUEST_METHOD: %s, HTTP_CONNECTION: %s, HTTP_UPGRADE: %s)" % [
    env["REQUEST_METHOD"], env["HTTP_CONNECTION"], env["HTTP_UPGRADE"]
  ]
end

#on_close(reason, code)

This method is for internal use only.
[ GitHub ]

  
# File 'actioncable/lib/action_cable/server/socket.rb', line 102

def on_close(reason, code) # :nodoc:
  send_async :handle_close
end

#on_error(message)

This method is for internal use only.
[ GitHub ]

  
# File 'actioncable/lib/action_cable/server/socket.rb', line 97

def on_error(message) # :nodoc:
  # log errors to make diagnosing socket errors easier
  logger.error "WebSocket error occurred: #{message}"
end

#on_message(message)

This method is for internal use only.
[ GitHub ]

  
# File 'actioncable/lib/action_cable/server/socket.rb', line 93

def on_message(message) # :nodoc:
  message_buffer.append message
end

#on_open

This method is for internal use only.
[ GitHub ]

  
# File 'actioncable/lib/action_cable/server/socket.rb', line 89

def on_open # :nodoc:
  send_async :handle_open
end

#perform_work(receiver, method, *args)

Invoke a method on the connection asynchronously through the pool of thread workers.

[ GitHub ]

  
# File 'actioncable/lib/action_cable/server/socket.rb', line 57

def perform_work(receiver, method, *args)
  worker_pool.async_invoke(receiver, method, *args, connection: self)
end

#process

This method is for internal use only.

Called by the server when a new Socket::WebSocket connection is established.

[ GitHub ]

  
# File 'actioncable/lib/action_cable/server/socket.rb', line 32

def process # :nodoc:
  logger.info started_request_message

  if websocket.possible? && server.allow_request_origin?(env)
    respond_to_successful_request
  else
    respond_to_invalid_request
  end
end

#receive(websocket_message)

This method is for internal use only.

Decodes WebSocket messages and dispatches them to subscribed channels. Socket::WebSocket message transfer encoding is always JSON.

[ GitHub ]

  
# File 'actioncable/lib/action_cable/server/socket.rb', line 75

def receive(websocket_message) # :nodoc:
  send_async :dispatch_websocket_message, websocket_message
end

#request

The request that initiated the Socket::WebSocket connection is available here. This gives access to the environment, cookies, etc.

[ GitHub ]

  
# File 'actioncable/lib/action_cable/server/socket.rb', line 66

def request
  @request ||= begin
    environment = Rails.application.env_config.merge(env) if defined?(Rails.application) && Rails.application
    ActionDispatch::Request.new(environment || env)
  end
end

#respond_to_invalid_request (private)

[ GitHub ]

  
# File 'actioncable/lib/action_cable/server/socket.rb', line 145

def respond_to_invalid_request
  close if websocket.alive?

  logger.error invalid_request_message
  logger.info finished_request_message
  [ 404, { Rack::CONTENT_TYPE => "text/plain; charset=utf-8" }, [ "Page not found" ] ]
end

#respond_to_successful_request (private)

[ GitHub ]

  
# File 'actioncable/lib/action_cable/server/socket.rb', line 140

def respond_to_successful_request
  logger.info successful_request_message
  websocket.rack_response
end

#send_async(method, *arguments)

[ GitHub ]

  
# File 'actioncable/lib/action_cable/server/socket.rb', line 61

def send_async(method, *arguments)
  worker_pool.async_invoke(self, method, *arguments)
end

#started_request_message (private)

[ GitHub ]

  
# File 'actioncable/lib/action_cable/server/socket.rb', line 153

def started_request_message
  'Started %s "%s"%s for %s at %s' % [
    request.request_method,
    request.filtered_path,
    websocket.possible? ? " [WebSocket]" : "[non-WebSocket]",
    request.ip,
    Time.now.to_s ]
end

#successful_request_message (private)

[ GitHub ]

  
# File 'actioncable/lib/action_cable/server/socket.rb', line 176

def successful_request_message
  "Successfully upgraded to WebSocket (REQUEST_METHOD: %s, HTTP_CONNECTION: %s, HTTP_UPGRADE: %s)" % [
    env["REQUEST_METHOD"], env["HTTP_CONNECTION"], env["HTTP_UPGRADE"]
  ]
end

#transmit(cable_message)

Send a non-serialized message over the Socket::WebSocket connection.

[ GitHub ]

  
# File 'actioncable/lib/action_cable/server/socket.rb', line 45

def transmit(cable_message)
  return unless websocket.alive?

  websocket.transmit encode(cable_message)
end