Ruby Ruby:CGI 和 FastCGI

rocLv · 2018年10月26日 · 最后由 rocLv 回复于 2018年10月29日 · 2923 次阅读

上一期的 RubyWeekly 里面有一篇文章,写的是Testing Ruby's CGI.

看到这篇文章的时候,特别有感,毕竟现在很多年轻的程序员估计没听过的CGI 这个词了 😃

听过过 CGI 的人,一定也听过 FastCGI。CGI 的全称是 Common Gateway Interface,或者称为通用网关接口。简单来说就是无论是 Apache,lighthttpd,Nginx 这样的 Web Server 程序都是只处理 HTML 以及其他文本文件,静态资源等。那么像 PHP 脚本,Perl 脚本,Ruby 等应用程序写成的文件,怎么能让它们自动输出成相应的 HTML 文件呢?这就是 CGI 干的事情。CGI 允许类似于 Apache 这样的 Web Server 可以调用外部的脚本作为 STDIN,然后按照一定的格式作为标准输出 STDOUT。

就 Ruby 来说,CGI 的问题在于每一个请求,都会启动一个 Ruby 进程。这对系统资源的损耗非常的大。因此就有了 FastCGI。快速通用网关接口(Fast Common Gateway Interface/FastCGI)是一种让交互程序与 Web 服务器通信的协议。FastCGI 是早期通用网关接口(CGI)的增强版本。 FastCGI 致力于减少网页服务器与 CGI 程序之间交互的开销,从而使服务器可以同时处理更多的网页请求。

简单背景就说到这里。

首先安装 Ruby 对应的 FastCGI gem, fcgi

$  brew install fastcgi
$ gem install fcgi

安装完成以后,开始配置 Apache。

Mac 系统自带 Apache,如果没有启动可以手动启动一下:

$ sudo apachectl start

系统默认是 80 端口,可以在浏览器输入http://localhost试试。

如果报错了,可以看看 Apache 的安装信息:

$ sudo apachectl -V

Server version: Apache/2.4.33 (Unix)
Server built:   Apr  3 2018 23:45:11
Server's Module Magic Number: 20120211:76
Server loaded:  APR 1.5.2, APR-UTIL 1.5.4
Compiled using: APR 1.5.2, APR-UTIL 1.5.4
Architecture:   64-bit
Server MPM:     prefork
  threaded:     no
    forked:     yes (variable process count)
Server compiled with....
 -D APR_HAS_SENDFILE
 -D APR_HAS_MMAP
 -D APR_HAVE_IPV6 (IPv4-mapped addresses enabled)
 -D APR_USE_FLOCK_SERIALIZE
 -D APR_USE_PTHREAD_SERIALIZE
 -D SINGLE_LISTEN_UNSERIALIZED_ACCEPT
 -D APR_HAS_OTHER_CHILD
 -D AP_HAVE_RELIABLE_PIPED_LOGS
 -D DYNAMIC_MODULE_LIMIT=256
 -D HTTPD_ROOT="/usr"
 -D SUEXEC_BIN="/usr/bin/suexec"
 -D DEFAULT_PIDLOG="/private/var/run/httpd.pid"
 -D DEFAULT_SCOREBOARD="logs/apache_runtime_status"
 -D DEFAULT_ERRORLOG="logs/error_log"
 -D AP_TYPES_CONFIG_FILE="/private/etc/apache2/mime.types"
 -D SERVER_CONFIG_FILE="/private/etc/apache2/httpd.conf"

这里会有 Apache 的详细信息。

启用 CGI

为了启用 cgi,我们需要修改/private/etc/apache2/httpd.conf这个文件:

打开这个文件后,搜索 cgi 会发现有这么一行:

ScriptAliasMatch ^/cgi-bin/((?!(?i:webobjects)).*$) "/Library/WebServer/CGI-Executables/$1"

这句表明我们的脚本应该放的位置,当然了,你也可以放在你想放的位置,然后修改一下这个路径。

AddHandler cgi-script .cgi

这行取消注释,就是把#号去了。

最关键的,这行前面的注释也一定要取消:

LoadModule cgi_module libexec/apache2/mod_cgi.so

保存后重启 Apache。

$ sudo apachectl restart

现在假如我们在目录/Library/WebServer/CGI-Executables/ 下创建一个文件 demo.rb:

#!/usr/bin/ruby
require 'cgi'

cgi = CGI.new

cgi.out("status" => "OK", "type" => "text/plain", "connection" => "close") do
  "Hello, Ruby!"
end

然后我们访问http://localhost/cgi-bin/demo.rb,会看到Hello , Ruby! 字样。

上面的是启用 CGI 使用 Ruby 的示例。当然,python,perl 文件也都可以。重要的是最上面一行代码,以及返回的格式。

接下来再说FastCGI, 原理基本差不多。

启用 FastCGI

首先我们要为 Apache 添加相应的模块: LoadModule fcgid_module /usr/local/libexec/apache2/mod_fcgid.so

因为默认 Apache 安装的位置,Mac 所谓的系统文件保护机制导致我们没法把相应的文件复制到/usr/libexec/apache2 下面,所以我把它放在了 local 目录下。

这时发现,因为我用的是 RVM,但是默认执行脚本用的是系统自带的 Ruby。

为了启用 RVM 安装的 ruby 版本,查了很多资料,最后没办法,去 Apache 官方看了一下,其实很简单:

#!/usr/bin/ruby 换成真是路径就可以了,修改后的文件demo.fcgi

#!/Users/wangqsh/.rvm/rubies/ruby-2.5.0/bin/ruby
require "fcgi"
FCGI.each {|request|
  out = request.out
  out.print "Content-Type: text/plain\r\n"
  out.print "\r\n"
  out.print Time.now.to_s
  request.finish
}

本以为可以休息一会了,谁知道提示找不到fcgi, 而这个 Gem 我确定安装过了。

后来只能再在 Apache 官方文档里查找,好在关于 CGI 的内容不多。

其实这里只需要在 Apache 配置文件里设置一下 GEM_PATH 就可以了:

直接在命令行:

$ echo $GEM_PATH

我以为把输出的结果,输入到 http.conf 文件中,设置环境变量用SetEnv 就可以了:

SetEnv GEM_PATH /Users/wangqsh/.rvm/gems/ruby-2.5.0:/Users/wangqsh/.rvm/gems/ruby-2.5.0@global

设置完之后重启 Apache

$ sudo apachectl restart

这时还报错 500,打开错误日志文件之后发现:

[Thu Oct 25 22:47:51.938104 2018] [fcgid:info] [pid 6673] mod_fcgid: server i.local:/Library/WebServer/CGI-Executables/server.fcgi(6704) started
/Users/wangqsh/.rvm/rubies/ruby-2.5.0/lib/ruby/2.5.0/rubygems/core_ext/kernel_require.rb:59:in `require': cannot load such file -- fcgi (LoadError)
    from /Users/wangqsh/.rvm/rubies/ruby-2.5.0/lib/ruby/2.5.0/rubygems/core_ext/kernel_require.rb:59:in `require'
    from /Library/WebServer/CGI-Executables/server.fcgi:2:in `<main>'
[Thu Oct 25 22:47:52.038811 2018] [fcgid:warn] [pid 6674] [client ::1:52385] mod_fcgid: error reading data, FastCGI server closed connection
[Thu Oct 25 22:47:52.038956 2018] [core:error] [pid 6674] [client ::1:52385] End of script output before headers: server.fcgi
[Thu Oct 25 22:47:58.006502 2018] [fcgid:info] [pid 6673] mod_fcgid: process /Library/WebServer/CGI-Executables/server.fcgi(6704) exit(communication error), terminated by calling exit(), return code: 1

还是路径设置有问题,最后发现应该如下配置:

<IfModule fcgid_module>
  FcgidInitialEnv PATH "/Users/wangqsh/.rvm/rubies/ruby-2.5.0/bin/ruby"
  FcgidInitialEnv GEM_PATH "/Users/wangqsh/.rvm/gems/ruby-2.5.0:/Users/wangqsh/.rvm/gems/ruby-2.5.0@global"
  AddHandler fcgid-script .fcgi
</IfModule>

SetEnv 指令只对CGI 有效,而FcgidInitialEnv 才会对FastCGI 有效。

访问http://localhost/cgi-bin/demo.fcgi 应该会正常显示当前时间了。

怎么样,是不是比较赞?

我的理解 CGI 就是一种协议或者标准

zouyu 回复

是的

我好像穿越到了 10 年前:系统是 Mac OS X 10.3,Ruby 是 1.8.2,社区在讨论 FastCGI。

这种运行和 apache 做 proxy 有什么区别?。。。同样都是 ruby 进程做 cgi

FastCGI,太原始了,可以考古一下 http://www.iteye.com/topic/296891

To summarize the facts:

  • DHH was using FastCGI from the beginning of Basecamp and Rails.
  • He had to do forced restarts 400 times a day because of leaking issues under FastCGI (really Ruby).
  • It wasn’t until Mongrel and later fastthread that people could actually deploy without problems…almost 2 years after Rails was widely being adopted.
  • This means that DHH and all of rails-core lied to everyone about Rails’ stability for years, and keep lying.
  • It also means that if you had problems with FastCGI, then you were not crazy. Ruby really was too unstable for production use.

嗯,因为 CGI/FastCGI 的各种问题,Python 搞了个 WSGI,而 Ruby 就是 Rack 了,应用服务器都是独立进程,靠反向代理的模式和 Web 服务器通信。

不过又有人嫌反向代理不够快,重走 FastCGI 这种由 Web 服务器调用应用服务器的模式,而且还更进一步把应用服务器内嵌了,所以又有 nginx-lua-module (周边搞多了就变成了 OpenResty)。

nouse 回复

只是受启发实验一下。 现在 Ruby 也不是以前的 Ruby 了,虽然我不知道他提到的leaking issues 是什么,但我相信应该早已经修复了

需要 登录 后方可回复, 如果你还没有账号请 注册新账号