上一期的 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,我们需要修改/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
, 原理基本差不多。
首先我们要为 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
应该会正常显示当前时间了。
怎么样,是不是比较赞?