为了Awstats给Nginx添加FastCGI方式的Perl支持

有个应用(awstats)需要Perl脚本支持,但是用的Nginx服务器,对Perl支持不好,于是通过FastCGI方式来使用Perl。

首先安装Perl的FCGI模块

  1. wget http://www.cpan.org/modules/by-module/FCGI/FCGI-0.67.tar.gz
  2. tar -zxvf FCGI-0.67.tar.gz
  3. cd FCGI-0.67
  4. perl Makefile.PL
  5. make && make install

还可以使用如下方法安装:

perl -MCPAN -e 'install FCGI'

安装FCGI-ProcManager

  1. wget http://search.cpan.org/CPAN/authors/id/G/GB/GBJK/FCGI-ProcManager-0.18.tar.gz
  2. tar -xzxf FCGI-ProcManager-0.18.tar.gz
  3. cd FCGI-ProcManager-0.18
  4. perl Makefile.PL
  5. make
  6. make install

Perl的FastCGI启动脚本

参考这篇文章 http://bbs.chinaunix.net/archiver/?tid-1224968.html

vi fcgi_perl
  1. #!/usr/bin/perl -w
  2. use FCGI;
  3. use Socket;
  4. use FCGI::ProcManager;
  5. sub shutdown { FCGI::CloseSocket($socket); exit; }
  6. sub restart { FCGI::CloseSocket($socket); &main; }
  7. use sigtrap 'handler', \&shutdown, 'normal-signals';
  8. use sigtrap 'handler', \&restart, 'HUP';
  9. require 'syscall.ph';
  10. use POSIX qw(setsid);
  11.  
  12. #&daemonize; we don't daemonize when running under runsv
  13. #this keeps the program alive or something after exec'ing perl scripts
  14. END() { }
  15. BEGIN() { }
  16. {
  17. no warnings;
  18. *CORE::GLOBAL::exit = sub { die "fakeexit\nrc=" . shift() . "\n"; };
  19. };
  20. eval q{exit};
  21. if ($@) {
  22. exit unless $@ =~ /^fakeexit/;
  23. }
  24. &main;
  25.  
  26. sub daemonize() {
  27. chdir '/' or die "Can't chdir to /: $!";
  28. defined( my $pid = fork ) or die "Can't fork: $!";
  29. exit if $pid;
  30. setsid() or die "Can't start a new session: $!";
  31. umask 0;
  32. }
  33.  
  34. sub main {
  35. #如果使用 IP sockets
  36. #$socket = FCGI::OpenSocket( "127.0.0.1:8999", 10 );
  37. #如果使用 UNIX sockets
  38. #$socket = FCGI::OpenSocket( "/var/run/perl_cgi-dispatch.sock", 10 );
  39.  
  40. #foreach $item (keys %ENV) { delete $ENV{$item}; }
  41. #设置fastcgi进程数,默认四个
  42. my $n_processes = $ENV{FCGI_NPROCESSES} || 4;
  43. $proc_manager = FCGI::ProcManager->new( {n_processes => $n_processes} );
  44. #使用unix socket
  45. $socket = FCGI::OpenSocket( "$ENV{FCGI_SOCKET_PATH}", 10 );
  46. #设置Socket权限
  47. chmod 0777, $ENV{FCGI_SOCKET_PATH};
  48.  
  49. ; #use UNIX sockets – user running this script must have w access to the 'nginx' folder!!
  50. $request =
  51. FCGI::Request( \*STDIN, \*STDOUT, \*STDERR, \%req_params, $socket,
  52. &FCGI::FAIL_ACCEPT_ON_INTR );
  53. $proc_manager->pm_manage();
  54. if ($request) { request_loop() }
  55. FCGI::CloseSocket($socket);
  56. }
  57.  
  58. sub request_loop {
  59. while ( $request->Accept() >= 0 ) {
  60. $proc_manager->pm_pre_dispatch();
  61.  
  62. #processing any STDIN input from WebServer (for CGI-POST actions)
  63. $stdin_passthrough = '';
  64. { no warnings; $req_len = 0 + $req_params{'CONTENT_LENGTH'}; };
  65. if ( ( $req_params{'REQUEST_METHOD'} eq 'POST' ) && ( $req_len != 0 ) )
  66. {
  67. my $bytes_read = 0;
  68. while ( $bytes_read < $req_len ) {
  69. my $data = '';
  70. my $bytes = read( STDIN, $data, ( $req_len$bytes_read ) );
  71. last if ( $bytes == 0 || !defined($bytes) );
  72. $stdin_passthrough .= $data;
  73. $bytes_read += $bytes;
  74. }
  75. }
  76.  
  77. #running the cgi app
  78. if (
  79. ( -x $req_params{SCRIPT_FILENAME} ) && #can I execute this?
  80. ( -s $req_params{SCRIPT_FILENAME} ) && #Is this file empty?
  81. ( -r $req_params{SCRIPT_FILENAME} ) #can I read this file?
  82. )
  83. {
  84. pipe( CHILD_RD, PARENT_WR );
  85. pipe( PARENT_ERR, CHILD_ERR );
  86. my $pid = open( CHILD_O, "-|" );
  87. unless ( defined($pid) ) {
  88. print("Content-type: text/plain\r\n\r\n");
  89. print
  90. "Error: CGI app returned no output – Executing $req_params{SCRIPT_FILENAME} failed !\n";
  91. next;
  92. }
  93. $oldfh = select(PARENT_ERR);
  94. $| = 1;
  95. select(CHILD_O);
  96. $| = 1;
  97. select($oldfh);
  98. if ( $pid > 0 ) {
  99. close(CHILD_RD);
  100. close(CHILD_ERR);
  101. print PARENT_WR $stdin_passthrough;
  102. close(PARENT_WR);
  103. $rin = $rout = $ein = $eout = '';
  104. vec( $rin, fileno(CHILD_O), 1 ) = 1;
  105. vec( $rin, fileno(PARENT_ERR), 1 ) = 1;
  106. $ein = $rin;
  107. $nfound = 0;
  108.  
  109. while ( $nfound =
  110. select( $rout = $rin, undef, $ein = $eout, 10 ) )
  111. {
  112. die "$!" unless $nfound != -1;
  113. $r1 = vec( $rout, fileno(PARENT_ERR), 1 ) == 1;
  114. $r2 = vec( $rout, fileno(CHILD_O), 1 ) == 1;
  115. $e1 = vec( $eout, fileno(PARENT_ERR), 1 ) == 1;
  116. $e2 = vec( $eout, fileno(CHILD_O), 1 ) == 1;
  117.  
  118. if ($r1) {
  119. while ( $bytes = read( PARENT_ERR, $errbytes, 4096 ) ) {
  120. print STDERR $errbytes;
  121. }
  122.  
  123. if ($!) {
  124. $err = $!;
  125. die $!;
  126. vec( $rin, fileno(PARENT_ERR), 1 ) = 0
  127. unless ( $err == EINTR or $err == EAGAIN );
  128. }
  129. }
  130. if ($r2) {
  131. while ( $bytes = read( CHILD_O, $s, 4096 ) ) {
  132. print $s;
  133. }
  134. if ( !defined($bytes) ) {
  135. $err = $!;
  136. die $!;
  137. vec( $rin, fileno(CHILD_O), 1 ) = 0
  138. unless ( $err == EINTR or $err == EAGAIN );
  139. }
  140. }
  141. last if ( $e1 || $e2 );
  142. }
  143. close CHILD_RD;
  144. close PARENT_ERR;
  145. waitpid( $pid, 0 );
  146. } else {
  147. foreach $key ( keys %req_params ) {
  148. $ENV{$key} = $req_params{$key};      
  149. }
  150.  
  151. # cd to the script's local directory
  152. if ( $req_params{SCRIPT_FILENAME} =~ /^(.*)\/[^\/]+$/ ) {
  153. chdir $1;
  154. }
  155. close(PARENT_WR);
  156.  
  157. #close(PARENT_ERR);
  158. close(STDIN);
  159. close(STDERR);
  160.  
  161. #fcntl(CHILD_RD, F_DUPFD, 0);
  162. syscall( &SYS_dup2, fileno(CHILD_RD), 0 );
  163. syscall( &SYS_dup2, fileno(CHILD_ERR), 2 );
  164.  
  165. #open(STDIN, "<&CHILD_RD");
  166. exec( $req_params{SCRIPT_FILENAME} );
  167. die("exec failed");
  168. }
  169. } else {
  170. print("Content-type: text/plain\r\n\r\n");
  171. print
  172. "Error: No such CGI app – $req_params{SCRIPT_FILENAME} may not exist or is not executable by this process.\n";
  173. }
  174. }
  175. }

再设置启动的脚本

vi startfcgiperl
  1. export FCGI_SOCKET_PATH="/tmp/perl_fcgi.socket"
  2. export FCGI_NPROCESSES=4
  3. ./fcgi_perl &

为Nginx添加FastCGI的Perl支持

编辑 nginx.conf 脚本,添加如下内容

location ~* .*\.pl$
{
include awstats.conf;
}
location /awstatsicon/
{
   alias /var/www/awstats-6.8/wwwroot/icon/;
}
vi awstats.conf
fastcgi_pass unix:/tmp/perl_fcgi.socket;
fastcgi_index awstats.pl;
#fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
#fastcgi_param SCRIPT_FILENAME /var/www/awstats-6.8/wwwroot/cgi-bin$fastcgi_script_name;
fastcgi_param SCRIPT_FILENAME /var/www/awstats-6.8/wwwroot/cgi-bin/awstats.pl;
fastcgi_param QUERY_STRING $query_string;
fastcgi_param REQUEST_METHOD $request_method;
fastcgi_param CONTENT_TYPE $content_type;
fastcgi_param CONTENT_LENGTH $content_length;
fastcgi_param GATEWAY_INTERFACE CGI/1.1;
fastcgi_param SERVER_SOFTWARE nginx;
fastcgi_param SCRIPT_NAME $fastcgi_script_name;
fastcgi_param REQUEST_URI $request_uri;
fastcgi_param DOCUMENT_URI $document_uri;
fastcgi_param DOCUMENT_ROOT $document_root;
fastcgi_param SERVER_PROTOCOL $server_protocol;
fastcgi_param REMOTE_ADDR $remote_addr;
fastcgi_param REMOTE_PORT $remote_port;
fastcgi_param SERVER_ADDR $server_addr;
fastcgi_param SERVER_PORT $server_port;
fastcgi_param SERVER_NAME $server_name;
fastcgi_read_timeout 60;

配置Awstats

运行 /var/www/awstats-6.8/tools/awstats_configure.pl 设置好一些变量

mkdir -p /etc/awstats
cp /var/www/awstats-6.8/wwwroot/awstats.model.conf /etc/awstats/awstats.my.site.com.conf

使用 vi /etc/awstats.my.site.com.conf 进行一些参数修改,设置icon目录,DirData数据存放目录,来源log日志文件路径等
使用 /var/www/awstats-6.8/tool/awstats_updateall.pl now -awstatsprog=/var/www/awstats-6.8/wwwroot/cgi-bin/awstats.pl 命令进行初始数据更新
使用 http://my.site.com/awstats.pl 查看数据

后续

将 ./awstats_updateall.pl now -awstatsprog=/var/www/awstats-6.8/wwwroot/cgi-bin/awstats.pl 放到crontab 中做定时更新,可以做一个脚本和日志文件的处理一起做。

碰到问题
在使用unix socket时,碰到两个问题
1.写socket的目录没有写权限,添加上解决
2.nginx连不上socket, 看日志提示没有权限,在创建了socket后添加一句
chmod 0777, $ENV{FCGI_SOCKET_PATH};
问题解决。

遗留对awstats.pl访问密码限制问题尚未解决好。
解决 在 nginx 中按如下配置方式

location /awstats {
  root /var/www/awstats-6.8/;
  include awstats.conf;
  auth_basic "awstats";
  auth_basic_user_file /var/www/.passwd;
}

其中 将 awstats-6.8 目录下的 wwwroot 修改为 awstats, /var/www/.passwd 为访问目录要用来验证的账号密码文件,用apache的提供的工具htpasswd制作而成

Popularity: 7% [?]

Related

Comments

8 Responses to “为了Awstats给Nginx添加FastCGI方式的Perl支持”

  1. Recent Faves Tagged With "fastcgi" : MyNetFaves on September 20th, 2008 9:50 am

    [...] public links >> fastcgi 为了Awstats给Nginx添加FastCGI方式的Perl支持 First saved by RedFerryDwarf69 | 2 days ago MySQL, PHP with FastCGI and Joomla on Windows [...]

  2. 沧海龙吟 » Blog Archive » Nignx配合Memcached提升400%性能(阅读笔记) on October 31st, 2008 11:31 am

    [...] August 31, 2008 — 为了Awstats给Nginx添加FastCGI方式的Perl支持 (1) [...]

  3. xi2008wang on October 31st, 2008 9:46 pm

    有两点疑问:
    1.
    vi startfcgiperlexport FCGI_SOCKET_PATH=”/tmp/perl_fcgi.socket”
    export FCGI_NPROCESSES=4
    ./fcgi_perl &

    这里fcgi_perl会输出到到控制台上.
    我使用1>/dev/null 2>&1 解决
    2.
    你的awstats能显示图片吗? 怎么解决的?

    [Reply]

    sunny Reply:

    你可以尝试修改awstats.model.conf 中的 DirIcons路径设置,在配合Web服务器中的路径设置,可以达到显示图片目的

    [Reply]

    xi2008wang Reply:

    多谢, 已解决.
    我是按awstats官方文档将icon复制到了根目录

    [Reply]

  4. wuming001 on November 8th, 2008 3:41 am

    .pl 后面可以加参数吗?

    [Reply]

    sunny Reply:

    什么意思?给 fcgi_perl 脚本加 命令行 参数 ?

    [Reply]

  5. wuming001 on November 14th, 2008 6:32 pm

    就是在浏览器地址栏里:像这样的链接

    http://sitename.com/xxx.pl?name=xxx&status=yyy&k=0

    我试了一下,不加后面那些get的参数是没问题的,但是加上了就不行了。

    [Reply]