在thttpd代码中,cgi是通过execve函数调用的
int execve(const char *filename, char *const argv[], char *const envp[]);
**execve()**执行程序由 filename决定。
filename必须是一个二进制的可执行文件,或者是一个脚本以#!格式开头的解释器参数参数。如果是后者,这个解释器必须是一个可执行的有效的路径名,但是不是脚本本身,它将调用解释器作为文件名。
argv是要调用的程序执行的参数序列,也就是我们要调用的程序需要传入的参数。
envp 同样也是参数序列,一般来说他是一种键值对的形式 key=value. 作为我们是新程序的环境。
所以,在调用cgi时如果运行了动态库,需要将动态库的环境包含在envp中,thttpd代码添加如下
//CGI_LD_LIBRARY_PATH即是运行的动态库环境,如果不定义该宏则是默认的动态路径
//(/opt/app/lib3rd:/opt/app/libsdk是我添加的动态库路径,并需要把#ifdef notdef屏蔽)
#ifdef notdef
#define CGI_LD_LIBRARY_PATH "/usr/local/lib:/usr/lib:/opt/app/lib3rd:/opt/app/libsdk"
#endif
如果cgi调用的函数中,包含了其他函数中的全局变量,那这个全局变量不生效,因为execve已经是一个独立的进程,和原本的进程没有关系了,所以全局变量也不会共用,即在a进程中全局变量的值,和execve进程中全局变量的值不相等
设置Cookie时,需在 printf(“Content-type:text/html\n\n”); 前设置,否则会当成response body
简单的cgi获取post数据可以通过stdin来获取,参考
https://blog.csdn.net/shallnet/article/details/50896985
然而,cgic库中,stdin的数据已经被处理过了,因此程序内再次读取sdin时并不能读到数据
可以用cgic中的 cgiFormEntries 或者 cgiFormString
当知道表单的名称时,可以使用cgiFormString,不知道时可以使用cgiFormEntries
//cgiFormString获取数据
int cgiMain() {
char name[241];
char number[241];
cgiHeaderContentType("text/html");
fprintf(cgiOut, "<HTML><HEAD>\n");
fprintf(cgiOut, "<TITLE>My CGI</TITLE></HEAD>\n");
fprintf(cgiOut, "<BODY>");
cgiFormString("name", name, 241);
cgiFormString("number", number, 241);
fprintf(cgiOut, "<H1>%s</H1>",name);
fprintf(cgiOut, "<H1>%s</H1>",number);
fprintf(cgiOut, "</BODY>\n");
fprintf(cgiOut, "</HTML>\n");
return 0;
}
//cgiFormEntries 获取数据
char **form;
cgiFormEntries(&form);
cgi_child( httpd_conn* hc )
{
int r;
char** argp;
char** envp;
char* binary;
char* directory;
/* Unset close-on-exec flag for this socket. This actually shouldn't
** be necessary, according to POSIX a dup()'d file descriptor does
** *not* inherit the close-on-exec flag, its flag is always clear.
** However, Linux messes this up and does copy the flag to the
** dup()'d descriptor, so we have to clear it. This could be
** ifdeffed for Linux only.
*/
(void) fcntl( hc->conn_fd, F_SETFD, 0 );
/* Close the syslog descriptor so that the CGI program can't
** mess with it. All other open descriptors should be either
** the listen socket(s), sockets from accept(), or the file-logging
** fd, and all of those are set to close-on-exec, so we don't
** have to close anything else.
*/
closelog();
/* If the socket happens to be using one of the stdin/stdout/stderr
** descriptors, move it to another descriptor so that the dup2 calls
** below don't screw things up. We arbitrarily pick fd 3 - if there
** was already something on it, we clobber it, but that doesn't matter
** since at this point the only fd of interest is the connection.
** All others will be closed on exec.
*/
if ( hc->conn_fd == STDIN_FILENO || hc->conn_fd == STDOUT_FILENO || hc->conn_fd == STDERR_FILENO )
{
int newfd = dup2( hc->conn_fd, STDERR_FILENO + 1 );
if ( newfd >= 0 )
hc->conn_fd = newfd;
/* If the dup2 fails, shrug. We'll just take our chances.
** Shouldn't happen though.
*/
}
/* Make the environment vector. */
envp = make_envp( hc );
/* Make the argument vector. */
argp = make_argp( hc );
/* Set up stdin. For POSTs we may have to set up a pipe from an
** interposer process, depending on if we've read some of the data
** into our buffer.
*/
if ( hc->method == METHOD_POST && hc->read_idx > hc->checked_idx )
{
int p[2];
if ( pipe( p ) < 0 )
{
HISON_LOG( LOG_ERR, "pipe - %m" );
httpd_send_err( hc, 500, err500title, "", err500form, hc->encodedurl );
httpd_write_response( hc );
exit( 1 );
}
r = fork( );
if ( r < 0 )
{
HISON_LOG( LOG_ERR, "fork - %m" );
httpd_send_err( hc, 500, err500title, "", err500form, hc->encodedurl );
httpd_write_response( hc );
exit( 1 );
}
if ( r == 0 )
{
/* Interposer process. */
sub_process = 1;
(void) close( p[0] );
cgi_interpose_input( hc, p[1] );
exit( 0 );
}
/* Need to schedule a kill for process r; but in the main process! */
(void) close( p[1] );
if ( p[0] != STDIN_FILENO )
{
(void) dup2( p[0], STDIN_FILENO );
(void) close( p[0] );
}
}
else
{
/* Otherwise, the request socket is stdin. */
if ( hc->conn_fd != STDIN_FILENO )
(void) dup2( hc->conn_fd, STDIN_FILENO );
}
/* Set up stdout/stderr. If we're doing CGI header parsing,
** we need an output interposer too.
*/
if ( strncmp( argp[0], "nph-", 4 ) != 0 && hc->mime_flag )
{
int p[2];
if ( pipe( p ) < 0 )
{
HISON_LOG( LOG_ERR, "pipe - %m" );
httpd_send_err( hc, 500, err500title, "", err500form, hc->encodedurl );
httpd_write_response( hc );
exit( 1 );
}
r = fork( );
if ( r < 0 )
{
HISON_LOG( LOG_ERR, "fork - %m" );
httpd_send_err( hc, 500, err500title, "", err500form, hc->encodedurl );
httpd_write_response( hc );
exit( 1 );
}
if ( r == 0 )
{
/* Interposer process. */
sub_process = 1;
(void) close( p[1] );
cgi_interpose_output( hc, p[0] );
exit( 0 );
}
/* Need to schedule a kill for process r; but in the main process! */
(void) close( p[0] );
if ( p[1] != STDOUT_FILENO )
(void) dup2( p[1], STDOUT_FILENO );
if ( p[1] != STDERR_FILENO )
(void) dup2( p[1], STDERR_FILENO );
if ( p[1] != STDOUT_FILENO && p[1] != STDERR_FILENO )
(void) close( p[1] );
}
else
{
/* 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 );
}
/* At this point we would like to set close-on-exec again for hc->conn_fd
** (see previous comments on Linux's broken behavior re: close-on-exec
** and dup.) Unfortunately there seems to be another Linux problem, or
** perhaps a different aspect of the same problem - if we do this
** close-on-exec in Linux, the socket stays open but stderr gets
** closed - the last fd duped from the socket. What a mess. So we'll
** just leave the socket as is, which under other OSs means an extra
** file descriptor gets passed to the child process. Since the child
** probably already has that file open via stdin stdout and/or stderr,
** this is not a problem.
*/
/* (void) fcntl( hc->conn_fd, F_SETFD, 1 ); */
#ifdef CGI_NICE
/* Set priority. */
(void) nice( CGI_NICE );
#endif /* CGI_NICE */
/* Split the program into directory and binary, so we can chdir()
** to the program's own directory. This isn't in the CGI 1.1
** spec, but it's what other HTTP servers do.
*/
directory = strdup( hc->expnfilename );
if ( directory == (char*) 0 )
binary = hc->expnfilename; /* ignore errors */
else
{
binary = strrchr( directory, '/' );
if ( binary == (char*) 0 )
binary = hc->expnfilename;
else
{
*binary++ = '\0';
(void) chdir( directory ); /* ignore errors */
}
}
//HISON_LOG( LOG_INFO, "binary:%s", binary );
/* Default behavior for SIGPIPE. */
#ifdef HAVE_SIGSET
(void) sigset( SIGPIPE, SIG_DFL );
#else /* HAVE_SIGSET */
(void) signal( SIGPIPE, SIG_DFL );
#endif /* HAVE_SIGSET */
/* Run the program. */
//HISON_LOG( LOG_INFO, "binary:%s argp:%s envp:%s", binary,argp[0],envp[0]);
(void) execve( binary, argp, envp );
/* Something went wrong. */
HISON_LOG( LOG_INFO, "execve %.80s - %m", hc->expnfilename );
httpd_send_err( hc, 500, err500title, "", err500form, hc->encodedurl );
httpd_write_response( hc );
_exit( 1 );
}
如上是cgi运行的主函数
步骤:
1、make_envp make_argp获取环境变量、运行参数
2、第一个if,创建管道,创建进程,子进程中获取http数据,父进程中将STDIN_FILENO重定向到p[0]
3、第二个if,创建管道,创建进程,子进程中返回http数据,父进程中将STDOUT_FILENO重定向到p[1]
4、execve调用cgi程序,cgi程序所输出的信息会被p[1]接收