如何使用Rails通过Web套接字发送二进制文件

我有一个Rails应用程序,用户上传音频文件。 我想将它们发送到第三方服务器,我需要使用Web套接字连接到外部服务器,因此,我需要我的Rails应用程序作为websocket客户端。

我正在试图找出如何正确设置它。 我还没有承诺任何gem,但’faye-websocket’gem看起来很有前途。 我甚至在“ 超时之前在websocket中发送大文件 ”中找到了类似的答案,但是,使用该代码对我来说不起作用。

以下是我的代码示例:

@message = Array.new EM.run { ws = Faye::WebSocket::Client.new("wss://example_url.com") ws.on :open do |event| File.open('path/to/audio_file.wav','rb') do |f| ws.send(f.gets) end end ws.on :message do |event| @message << [event.data] end ws.on :close do |event| ws = nil EM.stop end } 

当我使用它时,我从收件人服务器收到错误:

 No JSON object could be decoded 

这是有道理的,因为我不相信它为faye-websocket正确格式化。 他们的文件说:

send(message)接受字符串或字节大小的数组,并通过连接向另一个对等体发送文本或二进制消息; 二进制数据必须编码为数组。

我不知道如何做到这一点。 如何使用Ruby将二进制文件加载到整数数组中?

我尝试修改send命令以使用bytes方法:

 File.open('path/to/audio_file.wav','rb') do |f| ws.send(f.gets.bytes) end 

但是现在我收到了这个错误:

 Stream was 19 bytes but needs to be at least 100 bytes 

我知道我的文件是286KB,所以这里有问题。 我对何时使用File.read vs File.openFile.new

此外,也许这个gem不是发送二进制数据的最佳选择。 有没有人成功通过websockets在Rails中发送二进制文件?

更新:我确实找到了一种让它工作的方法,但它对于记忆来说很糟糕。 对于其他想要加载小文件的人,可以简单地使用File.binreadunpack方法:

 ws.on :open do |event| f = File.binread 'path/to/audio_file.wav' ws.send(f.unpack('C*')) end 

但是,如果我在仅100MB的文件上使用相同的代码,则服务器内存不足。 它在我的测试服务器上耗尽了整个可用的1.5GB! 有谁知道如何做到这一点是一种内存安全的方式?

这是我的看法:

 # do only once when initializing Rails: require 'iodine/client' Iodine.force_start! # this sets the callbacks. # on_message is always required by Iodine. options = {} options[:on_message] = Proc.new do |data| # this will never get called puts "incoming data ignored? for:\n#{data}" end options[:on_open] = Proc.new do # believe it or not - this variable belongs to the websocket connection. @started_upload = true # set a task to send the file, # so the on_open initialization doesn't block incoming messages. Iodine.run do # read the file and write to the websocket. File.open('filename','r') do |f| buffer = String.new # recycle the String's allocated memory write f.read(65_536, buffer) until f.eof? @started_upload = :done end # close the connection close end end options[:on_close] = Proc.new do |data| # can we notify the user that the file was uploaded? if @started_upload == :done # we did it :-) else # what happened? end end # will not wait for a connection: Iodine::Http.ws_connect "wss://example_url.com", options # OR # will wait for a connection, raising errors if failed. Iodine::Http::WebsocketClient.connect "wss://example_url.com", options 

我只是提到我是Iodine的作者,我写的是用于Plezi (一个RESTful Websocket实时应用程序框架,你可以独立使用或在Rails中使用)……我是超级偏见的;-)

我会避免gets因为它的大小可能包括整个文件或单个字节,具体取决于下一个行结束(EOL)标记的位置… read可以更好地控制每个块的大小。