博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
thttpd和cgilua安装与运行流程分析
阅读量:5992 次
发布时间:2019-06-20

本文共 10555 字,大约阅读时间需要 35 分钟。

安装

参考如下博文安装thttpd软件

 

thttpd配置文件:

root@fqs:/usr/local/bin# cat /usr/local/thttpd/

conf/ etc/  logs/ man/  sbin/ www/ 
root@fqs:/usr/local/bin# cat /usr/local/thttpd/conf/thttpd.conf

port=80

user=www
host=0.0.0.0
logfile=/usr/local/thttpd/logs/thttpd.log
pidfile=/usr/local/thttpd/logs/thttpd.pid
#throttles=/usr/local/thttpd/etc/throttle.conf
#urlpat=*.txt|*.mp3
#charset=utf-8
dir=/usr/local/thttpd/www
cgipat=/cgi-bin/*

 

cgilua采用luarocks安装。 其依赖 wsapi运行。

 

cgilua.cgi launcher:

root@fqs:/usr/local/bin# cat cgilua.cgi

#!/bin/sh

exec '/usr/bin/lua5.1' -e 'package.path="/root/.luarocks/share/lua/5.1/?.lua;/root/.luarocks/share/lua/5.1/?/init.lua;/usr/local/share/lua/5.1/?.lua;/usr/local/share/lua/5.1/?/init.lua;"..package.path; package.cpath="/root/.luarocks/lib/lua/5.1/?.so;/usr/local/lib/lua/5.1/?.so;"..package.cpath' -e 'local k,l,_=pcall(require,"luarocks.loader") _=k and l.add_context("cgilua","5.1.4-2")' '/usr/local/lib/luarocks/rocks/cgilua/5.1.4-2/bin/cgilua.cgi' "$@"

root@fqs:/usr/local/bin#
root@fqs:/usr/local/bin#
root@fqs:/usr/local/bin# cat /usr/local/lib/luarocks/rocks/cgilua/5.1.4-2/bin/cgilua.cgi
#!/usr/bin/env lua

-- CGILua (SAPI) launcher, extracts script to launch

-- either from the command line (use #!cgilua in the script)
-- or from SCRIPT_FILENAME/PATH_TRANSLATED
 
pcall(require, "luarocks.require")
 
local common = require "wsapi.common"
local cgi = require "wsapi.cgi"
 
local sapi = require "wsapi.sapi"
 
local arg_filename = (...)
 
local function sapi_loader(wsapi_env)
  common.normalize_paths(wsapi_env, arg_filename, "cgilua.cgi")
  return sapi.run(wsapi_env)
end
 
cgi.run(sapi_loader)
root@fqs:/usr/local/bin#

 

流程分析

thttpd配置遇到 cgipat规则的请求, 则启动cgi程序

cgipat规则为

cgipat=/cgi-bin/*

即, URL中含有  /cgi-bin/开头的文件名请求。

 

对应thttpd中的启动cgi子程序代码:

    /* Is it world-executable and in the CGI area? */

    if ( hc->hs->cgi_pattern != (char*) 0 &&
     ( hc->sb.st_mode & S_IXOTH ) &&
     match( hc->hs->cgi_pattern, hc->expnfilename ) )
    return cgi( hc );

 

启动cgi程序逻辑

cgi函数中,其实是fork了一个子进程

r = fork( );

if ( r < 0 )
    {
    syslog( LOG_ERR, "fork - %m" );
    httpd_send_err(
    hc, 500, err500title, "", err500form, hc->encodedurl );
    return -1;
    }
if ( r == 0 )
    {
    /* Child process. */
    sub_process = 1;
    httpd_unlisten( hc->hs );
    cgi_child( hc );
    }

 

cgi_child为子进程继续执行逻辑

1、 准备环境变量:

/* Make the environment vector. */
envp = make_envp( hc );

/* Make the argument vector. */

argp = make_argp( hc );

环境变量中, 包括若干 cgi参数:

static char**

make_envp( httpd_conn* hc )
    {
    static char* envp[50];
    int envn;
    char* cp;
    char buf[256];

    envn = 0;

    envp[envn++] = build_env( "PATH=%s", CGI_PATH );
#ifdef CGI_LD_LIBRARY_PATH
    envp[envn++] = build_env( "LD_LIBRARY_PATH=%s", CGI_LD_LIBRARY_PATH );
#endif /* CGI_LD_LIBRARY_PATH */
    envp[envn++] = build_env( "SERVER_SOFTWARE=%s", SERVER_SOFTWARE );
    if ( hc->hs->vhost && hc->hostname != (char*) 0 && hc->hostname[0] != '\0' )
    cp = hc->hostname;
    else if ( hc->hdrhost != (char*) 0 && hc->hdrhost[0] != '\0' )
    cp = hc->hdrhost;
    else if ( hc->reqhost != (char*) 0 && hc->reqhost[0] != '\0' )
    cp = hc->reqhost;
    else
    cp = hc->hs->server_hostname;
    if ( cp != (char*) 0 )
    envp[envn++] = build_env( "SERVER_NAME=%s", cp );
    envp[envn++] = "GATEWAY_INTERFACE=CGI/1.1";
    envp[envn++] = build_env("SERVER_PROTOCOL=%s", hc->protocol);
    (void) my_snprintf( buf, sizeof(buf), "%d", (int) hc->hs->port );
    envp[envn++] = build_env( "SERVER_PORT=%s", buf );
    envp[envn++] = build_env(
    "REQUEST_METHOD=%s", httpd_method_str( hc->method ) );
    if ( hc->pathinfo[0] != '\0' )
    {
    char* cp2;
    size_t l;
    envp[envn++] = build_env( "PATH_INFO=/%s", hc->pathinfo );
    l = strlen( hc->hs->cwd ) + strlen( hc->pathinfo ) + 1;
    cp2 = NEW( char, l );
    if ( cp2 != (char*) 0 )
        {
        (void) my_snprintf( cp2, l, "%s%s", hc->hs->cwd, hc->pathinfo );
        envp[envn++] = build_env( "PATH_TRANSLATED=%s", cp2 );
        }
    }
    envp[envn++] = build_env(
    "SCRIPT_NAME=/%s", strcmp( hc->origfilename, "." ) == 0 ?
    "" : hc->origfilename );
    if ( hc->query[0] != '\0')
    envp[envn++] = build_env( "QUERY_STRING=%s", hc->query );
    envp[envn++] = build_env(
    "REMOTE_ADDR=%s", httpd_ntoa( &hc->client_addr ) );
    if ( hc->referrer[0] != '\0' )
    {
    envp[envn++] = build_env( "HTTP_REFERER=%s", hc->referrer );
    envp[envn++] = build_env( "HTTP_REFERRER=%s", hc->referrer );
    }
    if ( hc->useragent[0] != '\0' )
    envp[envn++] = build_env( "HTTP_USER_AGENT=%s", hc->useragent );
    if ( hc->accept[0] != '\0' )
    envp[envn++] = build_env( "HTTP_ACCEPT=%s", hc->accept );
    if ( hc->accepte[0] != '\0' )
    envp[envn++] = build_env( "HTTP_ACCEPT_ENCODING=%s", hc->accepte );
    if ( hc->acceptl[0] != '\0' )
    envp[envn++] = build_env( "HTTP_ACCEPT_LANGUAGE=%s", hc->acceptl );
    if ( hc->cookie[0] != '\0' )
    envp[envn++] = build_env( "HTTP_COOKIE=%s", hc->cookie );
    if ( hc->contenttype[0] != '\0' )
    envp[envn++] = build_env( "CONTENT_TYPE=%s", hc->contenttype );
    if ( hc->hdrhost[0] != '\0' )
    envp[envn++] = build_env( "HTTP_HOST=%s", hc->hdrhost );
    if ( hc->contentlength != -1 )
    {
    (void) my_snprintf(
        buf, sizeof(buf), "%lu", (unsigned long) hc->contentlength );
    envp[envn++] = build_env( "CONTENT_LENGTH=%s", buf );
    }
    if ( hc->remoteuser[0] != '\0' )
    envp[envn++] = build_env( "REMOTE_USER=%s", hc->remoteuser );
    if ( hc->authorization[0] != '\0' )
    envp[envn++] = build_env( "AUTH_TYPE=%s", "Basic" );
    /* We only support Basic auth at the moment. */
    if ( getenv( "TZ" ) != (char*) 0 )
    envp[envn++] = build_env( "TZ=%s", getenv( "TZ" ) );
    envp[envn++] = build_env( "CGI_PATTERN=%s", hc->hs->cgi_pattern );

    envp[envn] = (char*) 0;

    return envp;
    }

 

 

2、 将连接fd设置为cgi程序的标准输入:

/* Otherwise, the request socket is stdin. */

if ( hc->conn_fd != STDIN_FILENO )
    (void) dup2( hc->conn_fd, STDIN_FILENO );

 

3、 将连接fd设置为cgi程序的标准输出 和 错误:

/* Otherwise, the request socket is stdout/stderr. */

if ( hc->conn_fd != STDOUT_FILENO )
    (void) dup2( hc->conn_fd, STDOUT_FILENO );
if ( hc->conn_fd != STDERR_FILENO )
    (void) dup2( hc->conn_fd, STDERR_FILENO );

 

4、 启动cgi的业务进程, 替代当前的 fork映像

/* Run the program. */

(void) execve( binary, argp, envp );

注意 环境变量已经被注入到 启动进程中, 即在业务进程中, 可以访问到 cgi参数。

包括当前脚本名称: SCRIPT_NAME

 

execv功能

The  exec()  family  of functions replaces the current process image with a new process image.  The functions described in this manual page are front-ends for execve(2).  (See the

manual page for execve(2) for further details about the replacement of the current process image.)

 

execve() executes the program pointed to by filename. filename must be either a binary executable, or a script starting with a line of the form "#! interpreter [arg]". In the latter case, the interpreter must be a valid pathname for an executable which is not itself a script, which will be invoked as interpreter [arg] filename.

如果待执行文件为脚本, 则启动脚本脚本的解释器程序, 并执行脚本。

 

脚本内容

Z:\cgilua-master\cgilua-master\examples\index.lp 样例中的此脚本

意思为 启动程序 env,  执行cgilua.cgi程序, 来处理脚本文件

#!/usr/bin/env cgilua.cgi

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"

   ">
<html xmlns=" xml:lang="en" lang="en">
<head>
    <title>Welcome to Kepler!</title>
    <link rel="stylesheet" href="css/doc.css" type="text/css"/>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
</head>

<body>

 

env命令程序的作用, 可以看出此处专门是用来 启动命令的(cgilua.cgi), 并没有设置环境变量

NAME
       env - run a program in a modified environment

SYNOPSIS

       env [OPTION]... [-] [NAME=VALUE]... [COMMAND [ARG]...]

DESCRIPTION

       Set each NAME to VALUE in the environment and run COMMAND.

       Mandatory arguments to long options are mandatory for short options too.

 

cgilua.cgi

cgilua.cgi 主要业务文件为 /usr/local/lib/luarocks/rocks/cgilua/5.1.4-2/bin/cgilua.cgi

依赖wsapi.cgi 和  wsapi.common 和 wsapi.cgi模块

root@fqs:/usr/local/bin# cat cgilua.cgi

#!/bin/sh

exec '/usr/bin/lua5.1' -e 'package.path="/root/.luarocks/share/lua/5.1/?.lua;/root/.luarocks/share/lua/5.1/?/init.lua;/usr/local/share/lua/5.1/?.lua;/usr/local/share/lua/5.1/?/init.lua;"..package.path; package.cpath="/root/.luarocks/lib/lua/5.1/?.so;/usr/local/lib/lua/5.1/?.so;"..package.cpath' -e 'local k,l,_=pcall(require,"luarocks.loader") _=k and l.add_context("cgilua","5.1.4-2")' '/usr/local/lib/luarocks/rocks/cgilua/5.1.4-2/bin/cgilua.cgi' "$@"

root@fqs:/usr/local/bin#
root@fqs:/usr/local/bin#
root@fqs:/usr/local/bin#
root@fqs:/usr/local/bin# cat /usr/local/lib/luarocks/rocks/cgilua/5.1.4-2/bin/cgilua.cgi
#!/usr/bin/env lua

-- CGILua (SAPI) launcher, extracts script to launch

-- either from the command line (use #!cgilua in the script)
-- or from SCRIPT_FILENAME/PATH_TRANSLATED
 
pcall(require, "luarocks.require")
 
local common = require "wsapi.common"
local cgi = require "wsapi.cgi"
 
local sapi = require "wsapi.sapi"
 
local arg_filename = (...)
 
local function sapi_loader(wsapi_env)
  common.normalize_paths(wsapi_env, arg_filename, "cgilua.cgi")
  return sapi.run(wsapi_env)
end
 
cgi.run(sapi_loader)
 #

 

 

1、 wsapi.cgi模块为 脚本入口, 其提供了 获取环境变量的通道, 设置到 wsapi_env表中:

并将前文中说的, cgi程序将  连接fd, 接管后, 作为标准输入 和 输出 以及错误的代表。

local os = require"os"

local io = require"io"
local common = require"wsapi.common"

common.setmode()

local _M = {}

-- Runs an WSAPI application for this CGI request

function _M.run(app_run)
   common.run(app_run, { input = io.stdin, output = io.stdout,
     error = io.stderr, env = os.getenv })
end

return _M

 

2、 wsapi.sapi 脚本实现, 启动cgilua执行的逻辑:

local response = require "wsapi.response"

local _M = {}

function _M.run(wsapi_env)

  _G.CGILUA_APPS = _G.CGILUA_APPS or wsapi_env.DOCUMENT_ROOT .. "/cgilua"
  _G.CGILUA_CONF = _G.CGILUA_CONF or wsapi_env.DOCUMENT_ROOT .. "/cgilua"
  _G.CGILUA_TMP = _G.CGILUA_TMP or os.getenv("TMP") or os.getenv("TEMP") or "/tmp"
  _G.CGILUA_ISDIRECT = true

  local res = response.new()

  _G.SAPI = {

    Info =  {
      _COPYRIGHT = "Copyright (C) 2007 Kepler Project",
      _DESCRIPTION = "WSAPI SAPI implementation",
      _VERSION = "WSAPI SAPI 1.0",
      ispersistent = false,
    },
    Request = {
      servervariable = function (name) return wsapi_env[name] end,
      getpostdata = function (n) return wsapi_env.input:read(n) end
    },
    Response = {
      contenttype = function (header)
        res:content_type(header)
      end,
      errorlog = function (msg, errlevel)
        wsapi_env.error:write (msg)
      end,
      header = function (header, value)
        if res.headers[header] then
          if type(res.headers[header]) == "table" then
            table.insert(res.headers[header], value)
          else
            res.headers[header] = { res.headers[header], value }
          end
        else
          res.headers[header] = value
        end
      end,
      redirect = function (url)
        res.status = 302
        res.headers["Location"] = url
      end,
      write = function (...)
        res:write({...})
      end,
    },
  }
  local cgilua = require "cgilua"
  cgilua.main()
  return res:finish()
end

return _M

 

至此, thttpd到cgilua的调用流程已经明确。

 

诚然, cgi运行模式, 为启动子进程处理请求, 对于每一个请求, 都会启动单独的cgi执行,执行完毕退出。

这样会有效率问题, 对于静态资源, 例如纯html和css图片等, 都不应该走cgi程序。

解决此问题的方法:

1、 将cgi脚本的处理 固定在 thttpd进程中处理。(是不是openresty是这种模式?)

2、 使用fastcgi代替。(下阶段研究)

转载地址:http://mgtlx.baihongyu.com/

你可能感兴趣的文章
nil、Nil、NULL和NSNull的理解
查看>>
第十二章 springboot + mongodb(复杂查询)
查看>>
Lintcode--009(单词切分)
查看>>
sqlite3中的数据类型 (转载)
查看>>
Atiitt 使用java语言编写sql函数或存储过程
查看>>
日志文件清理代码
查看>>
Maven属性(properties)标签的使用
查看>>
vim各种编码设置问题
查看>>
BOOST ASIO 学习专贴
查看>>
HTTP content-type
查看>>
知物由学 | AI时代,那些黑客正在如何打磨他们的“利器”?(一)
查看>>
Mysql 查询decimal 引号‘’增加精度
查看>>
Flex的UI组件Tile
查看>>
Java中对象的等价性比较
查看>>
SQL datediff 计算时间差
查看>>
网易有道面经(2013校园招聘杭州站)zz
查看>>
IOS中block和代理
查看>>
Codeforces Round #196 (Div. 2) A. Puzzles 水题
查看>>
Can only modify an image if it contains a bitmap
查看>>
[.net 面向对象程序设计进阶] (1) 开篇
查看>>