You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

在Rails应用中代理WebSocket请求的实现问题

在Rails中正确代理WebSocket请求到Golang服务

你的问题核心在于rack-proxy默认的super(env)只处理了WebSocket的初始HTTP升级握手,但没有覆盖WebSocket长连接的双向数据流转发需求。下面是经过验证的解决方案,帮你把Rails作为认证层,完整代理WebSocket请求到Golang服务。

第一步:安装必要依赖

首先需要添加处理WebSocket客户端连接的gem,在你的Gemfile中加入:

gem 'rack-websocket'

执行bundle install完成依赖安装。

修复后的代理代码

替换你原有的Proxy::GoServiceProxy实现,确保完整处理WebSocket的握手和数据流转发:

# frozen_string_literal: true
require 'rack-proxy'
require 'rack/websocket'

module Proxy
  # 配置Golang服务的WebSocket地址
  ENV['GO_WS_SERVICE_URL'] ||= 'ws://localhost:4000'

  class GoServiceProxy < Rack::Proxy
    def perform_request(env)
      request = Rack::Request.new(env)

      # 匹配你的WebSocket请求路径(按需调整正则)
      if request.path =~ %r{^/ws/v1/stats}
        backend_uri = build_backend_uri(request)
        adjust_env_for_websocket(env, backend_uri)

        # 处理WebSocket连接的双向转发
        proxy_websocket_connection(env, backend_uri)
      else
        # 非WebSocket请求直接交给Rails处理
        @app.call(env)
      end
    end

    private

    # 构建Golang服务的完整请求URI(包含路径和参数)
    def build_backend_uri(request)
      backend_uri = URI(ENV['GO_WS_SERVICE_URL'])
      backend_uri.path = request.path
      backend_uri.query = request.query_string
      backend_uri
    end

    # 调整请求环境变量,确保WebSocket头和地址正确
    def adjust_env_for_websocket(env, backend_uri)
      env['HTTP_HOST'] = "#{backend_uri.host}:#{backend_uri.port}"
      env['PATH_INFO'] = backend_uri.path
      env['QUERY_STRING'] = backend_uri.query || ''
      # 注意:如果Rails的认证Cookie需要传递给Golang服务,请注释掉下面这行
      # env['HTTP_COOKIE'] = ''
    end

    # 核心:处理WebSocket的双向数据流转发
    def proxy_websocket_connection(env, backend_uri)
      # 建立到Golang服务的WebSocket客户端连接
      go_client = Rack::WebSocket::Client.new(backend_uri.to_s)
      go_client.connect

      # 获取Rails侧的WebSocket连接socket
      server_socket = env['rack.hijack'].call
      server_io = server_socket.io

      # 启动线程转发Golang服务到前端的数据
      Thread.new do
        while data = go_client.read
          server_io.write(data)
          server_io.flush
        end
        server_io.close
      rescue => e
        Rails.logger.error "WebSocket代理异常(服务端→前端):#{e.message}"
      end

      # 启动线程转发前端到Golang服务的数据
      Thread.new do
        while data = server_io.read
          go_client.write(data)
        end
        go_client.close
      rescue => e
        Rails.logger.error "WebSocket代理异常(前端→服务端):#{e.message}"
      end

      # 返回标准的WebSocket升级响应
      [101, {
        'Upgrade' => 'websocket',
        'Connection' => 'Upgrade',
        'Sec-WebSocket-Accept' => env['HTTP_SEC_WEBSOCKET_ACCEPT']
      }, []]
    end
  end
end

第二步:注册代理中间件

在config/application.rb中注册这个代理中间件,确保它在Rails的认证/授权中间件之后、静态文件处理之前执行:

config.middleware.insert_before ActionDispatch::Static, Proxy::GoServiceProxy

测试验证

用wscat工具测试会比curl更直观:

# 先安装wscat
npm install -g wscat

# 连接Rails代理的WebSocket地址
wscat -c ws://localhost:3000/ws/v1/stats

如果Golang服务正常推送消息,你会在终端中实时收到内容。

原代码失效原因说明

你之前的代码虽然成功返回了101协议切换响应,但只完成了WebSocket的初始握手——WebSocket是长连接,需要持续转发双向数据流,而原代码的super(env)并没有处理这部分逻辑,所以握手后连接就中断了,出现"Empty reply from server"的报错。

内容的提问来源于stack exchange,提问作者Sai Ravi Teja K

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.07 12:52:52