Skip to content

WebSocket Support

Navigator provides comprehensive WebSocket support for real-time applications, including Action Cable integration, standalone WebSocket servers, and connection management.

Overview

Navigator supports WebSocket connections through multiple approaches:

  • Rails Action Cable: Standard Rails WebSocket integration
  • Standalone WebSocket Servers: External WebSocket services
  • Pattern-Based Routing: Flexible WebSocket endpoint matching
  • Connection Management: Proper connection lifecycle handling

Action Cable Integration

Basic Action Cable Setup

applications:
  tenants:
    - name: main-app
      path: /
      working_dir: /var/www/app

    # Dedicated Action Cable tenant
    - name: cable
      path: /cable
      working_dir: /var/www/app
      force_max_concurrent_requests: 0  # Unlimited connections for WebSockets

Rails Configuration

config/cable.yml
production:
  adapter: redis
  url: <%= ENV['REDIS_URL'] %>
  channel_prefix: myapp_production

development:
  adapter: redis
  url: redis://localhost:6379/1
config/routes.rb
Rails.application.routes.draw do
  # Action Cable endpoint
  mount ActionCable.server => '/cable'

  # Your other routes
  resources :messages
end
server:
  listen: 3000
  public_dir: /var/www/app/public

pools:
  max_size: 10
  idle_timeout: 600        # Longer timeout for persistent connections

applications:
  global_env:
    RAILS_ENV: production
    REDIS_URL: "${REDIS_URL}"
    ACTION_CABLE_ALLOWED_REQUEST_ORIGINS: "https://myapp.com"

  tenants:
    # Main HTTP application
    - name: web
      path: /
      working_dir: /var/www/app

    # Action Cable WebSocket connections
    - name: cable
      path: /cable
      working_dir: /var/www/app
      special: true    # Skip environment variable substitution
      env:
        RAILS_ENV: production
        ACTION_CABLE_URL: "ws://localhost:3000/cable"
      force_max_concurrent_requests: 0  # Allow unlimited WebSocket connections

managed_processes:
  - name: redis
    command: redis-server
    args: [/etc/redis/redis.conf]
    auto_restart: true

Standalone WebSocket Servers

External WebSocket Service

applications:
  tenants:
    - name: main-app
      path: /
      working_dir: /var/www/app

    # Proxy to external WebSocket server
    - name: websocket-service
      path: /ws/
      standalone_server: "localhost:8080"  # External WebSocket server

Multiple WebSocket Endpoints

applications:
  tenants:
    - name: web-app
      path: /

    # Different WebSocket services
    - name: chat-service
      path: /chat/
      standalone_server: "chat-server:9000"

    - name: notifications
      path: /notifications/
      standalone_server: "notification-server:9001"

    - name: live-updates
      path: /live/
      standalone_server: "update-server:9002"

Pattern-Based WebSocket Routing

Flexible Path Matching

applications:
  tenants:
    # Match any path ending with /socket
    - name: websocket-wildcard
      path: /api/
      match_pattern: "*/socket"
      working_dir: /var/www/app
      force_max_concurrent_requests: 0

    # Match tenant-specific WebSocket paths
    - name: tenant-websockets
      path: /tenants/
      match_pattern: "/tenants/*/ws"
      working_dir: /var/www/app

Multi-Tenant WebSocket Setup

applications:
  env:
    TENANT_ID: "${tenant_id}"
    WEBSOCKET_PATH: "/tenants/${tenant_id}/ws"

  tenants:
    - name: tenant-a-ws
      path: /tenants/a/ws
      working_dir: /var/www/app
      var:
        tenant_id: "tenant_a"
      force_max_concurrent_requests: 0

    - name: tenant-b-ws  
      path: /tenants/b/ws
      working_dir: /var/www/app
      var:
        tenant_id: "tenant_b"
      force_max_concurrent_requests: 0

Connection Management

Connection Limits

pools:
  max_size: 20             # More processes for WebSocket connections
  idle_timeout: 1800       # Longer timeout (30 minutes)

applications:
  tenants:
    # Regular HTTP requests - limited concurrency
    - name: web
      path: /
      max_concurrent_requests: 5

    # WebSocket connections - unlimited
    - name: websockets
      path: /ws/
      force_max_concurrent_requests: 0  # No limit for persistent connections

Connection Monitoring

# Enable debug logging for connection tracking
applications:
  global_env:
    LOG_LEVEL: debug
    ACTION_CABLE_LOG_LEVEL: debug

Production Configuration

High-Performance WebSocket Setup

server:
  listen: 3000
  public_dir: /var/www/app/public

pools:
  max_size: 30             # More processes for high connection count
  idle_timeout: 3600       # 1 hour - keep connections alive
  start_port: 4000

# Disable suspension for WebSocket servers
suspend:
  enabled: false           # WebSockets prevent proper suspension

applications:
  global_env:
    RAILS_ENV: production
    REDIS_URL: "${REDIS_URL}"
    ACTION_CABLE_ALLOWED_REQUEST_ORIGINS: "${ALLOWED_ORIGINS}"

    # WebSocket-specific settings
    ACTION_CABLE_WORKER_POOL_SIZE: "10"
    ACTION_CABLE_MAX_REQUEST_SIZE: "1048576"  # 1MB

  tenants:
    - name: web
      path: /
      working_dir: /var/www/app
      max_concurrent_requests: 10

    - name: websockets
      path: /cable
      working_dir: /var/www/app
      force_max_concurrent_requests: 0
      env:
        ACTION_CABLE_MOUNT_PATH: "/cable"

managed_processes:
  - name: redis
    command: redis-server
    args: [--maxmemory, 512mb, --maxmemory-policy, allkeys-lru]
    auto_restart: true

Security Configuration

Origin Validation

applications:
  global_env:
    # Restrict WebSocket origins in production
    ACTION_CABLE_ALLOWED_REQUEST_ORIGINS: "https://myapp.com,https://www.myapp.com"

    # Development - allow all origins
    # ACTION_CABLE_ALLOWED_REQUEST_ORIGINS: "*"
config/environments/production.rb
# Action Cable security
config.action_cable.allowed_request_origins = [
  'https://myapp.com',
  'https://www.myapp.com'
]

# Use secure WebSocket URLs
config.action_cable.url = 'wss://myapp.com/cable'

Authentication Integration

app/channels/application_cable/connection.rb
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
      # Authentication via session
      if verified_user = User.find_by(id: session[:user_id])
        verified_user
      else
        reject_unauthorized_connection
      end
    end
  end
end

Load Balancing and Scaling

Multi-Instance WebSocket Setup

# Instance 1 - Primary WebSocket server
server:
  listen: 3001

applications:
  tenants:
    - name: websocket-primary
      path: /cable
      force_max_concurrent_requests: 0

# Instance 2 - Secondary WebSocket server  
server:
  listen: 3002

applications:
  tenants:
    - name: websocket-secondary
      path: /cable
      force_max_concurrent_requests: 0

Load Balancer Configuration

nginx.conf - WebSocket Load Balancing
upstream websocket_backend {
    # Enable session persistence for WebSockets
    ip_hash;

    server 127.0.0.1:3001;
    server 127.0.0.1:3002;
}

server {
    listen 80;
    server_name myapp.com;

    # WebSocket proxy
    location /cable {
        proxy_pass http://websocket_backend;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;

        # WebSocket specific timeouts
        proxy_read_timeout 300s;
        proxy_send_timeout 300s;
    }

    # Regular HTTP traffic
    location / {
        proxy_pass http://websocket_backend;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

Monitoring WebSocket Connections

Connection Metrics

#!/bin/bash
# WebSocket connection monitoring

# Count active WebSocket connections
websocket_connections=$(netstat -an | grep :3000 | grep ESTABLISHED | wc -l)
echo "websocket_connections $websocket_connections"

# Monitor Rails processes handling WebSockets
rails_processes=$(pgrep -f "rails server" | wc -l)
echo "websocket_rails_processes $rails_processes"

# Check Redis connections (for Action Cable)
redis_connections=$(redis-cli info clients | grep connected_clients | cut -d: -f2)
echo "websocket_redis_connections $redis_connections"

# Memory usage of WebSocket processes
websocket_memory=$(ps aux | grep -E "(rails|navigator)" | awk '{sum+=$6} END {print sum}')
echo "websocket_memory_kb $websocket_memory"

Action Cable Monitoring

config/initializers/action_cable_monitoring.rb
# Monitor Action Cable performance
ActionCable.server.config.logger = Rails.logger

# Custom metrics for Action Cable
Rails.application.config.after_initialize do
  ActionCable.server.config.connection_class.prepend(Module.new do
    def connect
      Rails.logger.info "ActionCable connection established: #{request.remote_ip}"
      super
    end

    def disconnect
      Rails.logger.info "ActionCable connection closed: #{request.remote_ip}"
      super
    end
  end)
end

Troubleshooting

WebSocket Connection Issues

Issue: WebSocket connections fail to establish

Debug Steps:

# Check Navigator WebSocket support
curl -i -N -H "Connection: Upgrade" -H "Upgrade: websocket" \
     -H "Sec-WebSocket-Version: 13" -H "Sec-WebSocket-Key: test" \
     http://localhost:3000/cable

# Check Rails Action Cable
rails console
> ActionCable.server.config.logger = Logger.new(STDOUT)

# Test WebSocket connection
wscat -c ws://localhost:3000/cable

High Memory Usage

Issue: WebSocket connections cause high memory usage

Solutions:

# Limit connection lifetime
applications:
  global_env:
    ACTION_CABLE_CONNECTION_TIMEOUT: "300"  # 5 minutes

# Use Redis for connection state
applications:
  global_env:
    REDIS_URL: "${REDIS_URL}"
    ACTION_CABLE_ADAPTER: "redis"

Connection Drops

Issue: WebSocket connections drop frequently

Solutions:

# Increase timeouts
pools:
  idle_timeout: 3600       # 1 hour

# Disable suspension for WebSocket servers
suspend:
  enabled: false

# Configure keepalive
applications:
  global_env:
    ACTION_CABLE_KEEPALIVE: "30"  # 30 seconds

Integration Examples

Real-Time Chat Application

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

  def receive(data)
    ActionCable.server.broadcast("chat_#{params[:room]}", {
      message: data['message'],
      user: current_user.name,
      timestamp: Time.current
    })
  end
end
navigator.yml
applications:
  tenants:
    - name: web
      path: /

    - name: chat
      path: /cable
      force_max_concurrent_requests: 0
      env:
        ACTION_CABLE_ALLOWED_REQUEST_ORIGINS: "https://myapp.com"

Live Dashboard Updates

app/channels/dashboard_channel.rb
class DashboardChannel < ApplicationCable::Channel
  def subscribed
    stream_from "dashboard_#{current_user.id}"
  end
end

# Broadcast updates from controllers
class MetricsController < ApplicationController
  def update
    # Update metrics logic

    ActionCable.server.broadcast("dashboard_#{current_user.id}", {
      metrics: updated_metrics,
      timestamp: Time.current
    })
  end
end

Notification System

app/channels/notifications_channel.rb
class NotificationsChannel < ApplicationCable::Channel
  def subscribed
    stream_from "notifications_#{current_user.id}"
  end
end

# Background job to send notifications
class NotificationJob < ApplicationJob
  def perform(user_id, notification)
    ActionCable.server.broadcast("notifications_#{user_id}", notification)
  end
end

Best Practices

1. Connection Limits

# Set appropriate limits
applications:
  tenants:
    - name: websockets
      force_max_concurrent_requests: 0  # Unlimited for WebSockets
    - name: web
      max_concurrent_requests: 10       # Limited for HTTP

2. Resource Management

# Allocate sufficient resources
pools:
  max_size: 25             # More processes for connections
  idle_timeout: 3600       # Longer timeouts for persistent connections

3. Security

# Validate origins in production
applications:
  global_env:
    ACTION_CABLE_ALLOWED_REQUEST_ORIGINS: "https://yourdomain.com"

4. Monitoring

# Monitor connection health
netstat -an | grep :3000 | grep ESTABLISHED | wc -l
redis-cli info clients

5. Graceful Degradation

// Client-side reconnection logic
const cable = ActionCable.createConsumer('/cable');

cable.subscriptions.create('ChatChannel', {
  connected() {
    console.log('WebSocket connected');
  },

  disconnected() {
    console.log('WebSocket disconnected - attempting reconnect');
    // Implement exponential backoff reconnection
  },

  received(data) {
    // Handle received data
  }
});

See Also