使用 Ruby 将控制台应用程序公开到网络

发布于 2024-08-23 14:30:14 字数 574 浏览 9 评论 0 原文

我希望通过 JSON 或使用 Ruby 的其他 RPC 样式服务公开交互式命令行程序。我找到了一些技巧来做到这一点,但是在重定向输出和输入时我遗漏了一些东西。

至少在 Linux 上的一种方法是将 stdin 和 stdout 重定向到一个文件,然后与文件读取和写入异步读取和写入该文件。在谷歌搜索后我一直在尝试的另一种方法是使用 open4。这是我到目前为止编写的代码,但在从标准输出读取几行后它就卡住了。

require "open4"
include Open4

status = popen4("./srcds_run -console -game tf +map ctf_2fort -maxplayers 6") do |pid, stdin, stdout, stderr|
  puts "PID #{pid}"
  lines=""
  while (line=stdout.gets)
    lines+=line
    puts line
  end
  while (line=stderr.gets)
    lines+=line
    puts line
  end
end

对此的任何帮助或一些见解将不胜感激!

I'm looking to expose an interactive command line program via JSON or another RPC style service using Ruby. I've found a couple tricks to do this, but im missing something when redirecting the output and input.

One method at least on linux is to redirect the stdin and stdout to a file then read and write to that file asynchronously with file reads and writes. Another method ive been trying after googling around was to use open4. Here is the code I wrote so far, but its getting stuck after reading a few lines from standard output.

require "open4"
include Open4

status = popen4("./srcds_run -console -game tf +map ctf_2fort -maxplayers 6") do |pid, stdin, stdout, stderr|
  puts "PID #{pid}"
  lines=""
  while (line=stdout.gets)
    lines+=line
    puts line
  end
  while (line=stderr.gets)
    lines+=line
    puts line
  end
end

Any help on this or some insight would be appreciated!

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

昵称有卵用 2024-08-30 14:30:14

我建议使用 Xinetd (或类似的)在某个套接字上运行命令,然后使用 ruby​​ 网络代码。您在此处的代码中已经遇到的问题之一是您的两个 while 循环是连续的,这可能会导致问题。

What I would recommend is using Xinetd (or similar) to run the command on some socket and then using the ruby network code. One of the problems you've already run into in your code here is that your two while loops are sequential, which can cause problems.

十年不长 2024-08-30 14:30:14

您可以尝试的另一个技巧是在命令中将 stderr 重定向到 stdout,以便您的程序只需读取 stdout。像这样的事情:

popen4("./srcds_run -console -game tf +map ctf_2fort -maxplayers 6 2>&1")

这样做的另一个好处是您可以按照程序运行期间发生的顺序获取所有消息/错误。

Another trick you might try is to re-direct stderr to stdout in your command, so that your program only has to read the stdout. Something like this:

popen4("./srcds_run -console -game tf +map ctf_2fort -maxplayers 6 2>&1")

The other benefit of this is that you get all the messages/errors in the order they happen during the program run.

凯凯我们等你回来 2024-08-30 14:30:14

编辑

您应该考虑AnyTerm集成。然后,您可以直接公开 AnyTerm,例如通过 Apache mod_proxy,或者让 Rails 控制器充当反向代理(处理身份验证/会话验证,然后回放 controller.request 减去任何 cookie 到 localhost:,并作为响应发送回 AnyTerm 回复的内容。)

class ConsoleController < ApplicationController
  # AnyTerm speaks via HTTP POST only
  def update
    # validate session
    ...
    # forward request to AnyTerm
    response = Net::HTTP.post_form(URI.parse('http://localhost:#{AnyTermPort}/', request.params))
    headers['Content-Type'] = response['Content-Type']
    render_text response.body, response.status
  end

否则,您需要使用 IO::SelectIO::read_noblock 了解何时可以读取数据(从网络或子进程),这样就不会死锁。另请参阅。另请检查您的 Rails 是否在多线程环境中使用或者您的 Ruby 版本是否不受 此 IO::Select 错误

您可以从以下几行开始:

status = POpen4::popen4("ping localhost") do |stdout, stderr, stdin, pid|  
  puts "PID #{pid}"  
  # our buffers 
  stdout_lines="" 
  stderr_lines=""
  begin
    loop do
      # check whether stdout, stderr or both are 
      #  ready to be read from without blocking 
      IO.select([stdout,stderr]).flatten.compact.each { |io|
        # stdout, if ready, goes to stdout_lines 
        stdout_lines += io.readpartial(1024) if io.fileno == stdout.fileno 
        # stderr, if ready, goes to stdout_lines 
        stderr_lines += io.readpartial(1024) if io.fileno == stderr.fileno 
      }
      break if stdout.closed? && stderr.closed? 
      # if we acumulated any complete lines (\n-terminated) 
      #  in either stdout/err_lines, output them now 
      stdout_lines.sub!(/.*\n/m) { puts 
amp; ; '' } 
      stderr_lines.sub!(/.*\n/m) { puts 
amp; ; '' } 
    end
  rescue EOFError
    puts "Done"
  end 
end 

要同时处理 stdin,请更改为:

      IO.select([stdout,stderr],[stdin]).flatten.compact.each { |io|
        # program ready to get stdin? do we have anything for it?
        if io.fileno == stdin.fileno && <got data from client?>
          <write a small chunk from client to stdin>
        end
        # stdout, if ready, goes to stdout_lines 

EDIT

Your should consider integrating with AnyTerm. You can then either expose AnyTerm directly e.g. via Apache mod_proxy, or have your Rails controller act as the reverse proxy (handling authentication/session validation, then playing back controller.request minus any cookies to localhost:<AnyTerm-daemon-port>, and sending back as a response whatever AnyTerm replies with.)

class ConsoleController < ApplicationController
  # AnyTerm speaks via HTTP POST only
  def update
    # validate session
    ...
    # forward request to AnyTerm
    response = Net::HTTP.post_form(URI.parse('http://localhost:#{AnyTermPort}/', request.params))
    headers['Content-Type'] = response['Content-Type']
    render_text response.body, response.status
  end

Otherwise, you'd need to use IO::Select or IO::read_noblock to know when data is available to be read (from either network or subprocess) so you don't deadlock. See this too. Also check that either your Rails is used in a multi-threaded environment or that your Ruby version is not affected by this IO::Select bug.

You can start with something along these lines:

status = POpen4::popen4("ping localhost") do |stdout, stderr, stdin, pid|  
  puts "PID #{pid}"  
  # our buffers 
  stdout_lines="" 
  stderr_lines=""
  begin
    loop do
      # check whether stdout, stderr or both are 
      #  ready to be read from without blocking 
      IO.select([stdout,stderr]).flatten.compact.each { |io|
        # stdout, if ready, goes to stdout_lines 
        stdout_lines += io.readpartial(1024) if io.fileno == stdout.fileno 
        # stderr, if ready, goes to stdout_lines 
        stderr_lines += io.readpartial(1024) if io.fileno == stderr.fileno 
      }
      break if stdout.closed? && stderr.closed? 
      # if we acumulated any complete lines (\n-terminated) 
      #  in either stdout/err_lines, output them now 
      stdout_lines.sub!(/.*\n/m) { puts 
amp; ; '' } 
      stderr_lines.sub!(/.*\n/m) { puts 
amp; ; '' } 
    end
  rescue EOFError
    puts "Done"
  end 
end 

To also handle stdin, change to:

      IO.select([stdout,stderr],[stdin]).flatten.compact.each { |io|
        # program ready to get stdin? do we have anything for it?
        if io.fileno == stdin.fileno && <got data from client?>
          <write a small chunk from client to stdin>
        end
        # stdout, if ready, goes to stdout_lines 
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文