在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
相关产品推荐
相关产品推荐

