Thttpd源程序解析18 thttpd請求處理過程詳解之CGI過程

從之前講述的really_start_request函數我們知道當設置的cgi_pattern不為空且文件具有其他用戶可執行的權限且cgi_pattern與文件的擴展名稱匹配時將調用CGI過程。

CGI過程根據用戶的請求方式和請求參數調用存在於cgi_pattern文件目錄中的可執行程序返回用戶數據的過程。

程序流程

(1)判斷已經使用的CGI的程序數量是否超過了設置的最大數量,如果是退出函數,反之繼續執行。

(2)設置當前使用的CGI的程序數量加一。

(3)設置連接的文件描述符為阻塞模式。

(4)創建子進程,如果創建失敗退出函數;如果創建成功子進程關閉服務器的文件描述符,調用cgi_child函數;父進程設置子進程超時處理函數,設置狀態碼等參數然後退出。

(5)設置連接的文件描述符的F_SETFD值為0。

(6)如果連接的文件描述符為標準輸入輸出或者是標準錯誤賦值連接的文件描述符為3。

(7)設置環境變量

(8)設置參數變量

(9)對於方式是POST方式,創建管道,創建失敗返回500錯誤退出函數,創建成功將會創建子進程,創建子進程失敗返回500錯誤退出函數,創建成功讀取未讀取完的數據寫到管道的寫數據端口,如果讀管道的文件描述符不是標準輸入複製為標準輸入。對於不是POST方式設置連接的文件描述符的值為標準輸入。

(10)複製連接的文件描述符到標準輸入和標準錯誤

(10)設置子程序運行的優先級

(11)設置子程序的名稱。

(12)調用子程序。

流程圖

Thttpd源程序解析18 thttpd請求處理過程詳解之CGI過程

源程序

/**cgi程序處理*/
static int cgi( httpd_conn* hc )
{
int r;
ClientData client_data;
\t/**判斷是否是有效的CGI程序*/
if ( hc->hs->cgi_limit != 0 && hc->hs->cgi_count >= hc->hs->cgi_limit )
\t{
\t\thttpd_send_err(hc, 503, httpd_err503title, "", httpd_err503form,hc->encodedurl );
\t\treturn -1;
\t}
++hc->hs->cgi_count;
\t/**設置為非阻塞模式*/
httpd_clear_ndelay( hc->conn_fd );
\t/**創建子進程*/
r = fork( );
if ( r < 0 )
\t{
\t\tsyslog( LOG_ERR, "fork - %m" );
\t\thttpd_send_err(hc, 500, err500title, "", err500form, hc->encodedurl );
\t\treturn -1;
\t}
\t/**子進程處理函數*/
if ( r == 0 )
\t{
\t/* Child process. */
\t\tsub_process = 1;
\t\thttpd_unlisten( hc->hs );
\t\tcgi_child( hc );
\t}
\t/**父進程處理函數*/
/* Parent process. */
syslog( LOG_DEBUG, "spawned CGI process %d for file '%.200s'", r, hc->expnfilename );
#ifdef CGI_TIMELIMIT
/* Schedule a kill for the child process, in case it runs too long */
client_data.i = r;
\t/**設置子進程程序運行的超時時間*/
if ( tmr_create( (struct timeval*) 0, cgi_kill, client_data, CGI_TIMELIMIT * 1000L, 0 ) == (Timer*) 0 )
\t{
\t\tsyslog( LOG_CRIT, "tmr_create(cgi_kill child) failed" );
\t\texit( 1 );

\t}
#endif /* CGI_TIMELIMIT */
hc->status = 200;
hc->bytes_sent = CGI_BYTECOUNT;
hc->should_linger = 0;
return 0;
}
/**CGI處理函數*/
static void 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.
*/
/**設置這樣的話將會保證打開的文件描述符在子進程中調用exec相關函數時保持打開 */
(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 )
\t{
\t\tint newfd = dup2( hc->conn_fd, STDERR_FILENO + 1 );
\t\tif ( newfd >= 0 )
\t {
\t\t\thc->conn_fd = newfd;
\t\t}
\t/* If the dup2 fails, shrug. We'll just take our chances.
\t** Shouldn't happen though.

\t*/
\t}
/* Make the environment vector. */
\t/**設置環境變量*/
envp = make_envp( hc );
/* Make the argument vector. */
\t/**設置參數變量*/
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.
*/
\t/**對於POST方式且需要讀取的數據大於已經讀取的數據的處理*/
if ( hc->method == METHOD_POST && hc->read_idx > hc->checked_idx )
\t{
\t\tint p[2];
\t\t/**創建管道失敗的處理*/
\t\tif ( pipe( p ) < 0 )
\t {
\t \tsyslog( LOG_ERR, "pipe - %m" );
\t \thttpd_send_err( hc, 500, err500title, "", err500form, hc->encodedurl );
\t \thttpd_write_response( hc );
\t \texit( 1 );
\t }
\t\t/**創建管道成功*/
\t\tr = fork( );
\t\t/**創建子進程失敗處理*/
\t\tif ( r < 0 )
\t {
\t \tsyslog( LOG_ERR, "fork - %m" );
\t \thttpd_send_err( hc, 500, err500title, "", err500form, hc->encodedurl );
\t \thttpd_write_response( hc );
\t \texit( 1 );
\t }
\t\t/**創建子進程成功的處理*/
\t\tif ( r == 0 )
\t {
\t \t/* Interposer process. */
\t \tsub_process = 1;
\t\t\t/**關閉讀管道*/
\t \t(void) close( p[0] );
\t\t\t/***/
\t \tcgi_interpose_input( hc, p[1] );

\t \texit( 0 );
\t }
\t\t/* Need to schedule a kill for process r; but in the main process! */
\t\t(void) close( p[1] );
\t\tif ( p[0] != STDIN_FILENO )
\t {
\t \t(void) dup2( p[0], STDIN_FILENO );
\t \t(void) close( p[0] );
\t }
\t}
else
\t{
\t\t/* Otherwise, the request socket is stdin. */
\t\t/**對於連接的文件描述符不是標準輸入文件描述符的處理
\t\t * 將當前的文件描述符的複製給標準輸入文件描述符
\t\t*/
\t\tif ( hc->conn_fd != STDIN_FILENO )
\t {
\t\t\t(void) dup2( hc->conn_fd, STDIN_FILENO );
\t\t}
\t}
/* 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 )
\t{
\t\tint p[2];
\t\tif ( pipe( p ) < 0 )
\t {
\t \tsyslog( LOG_ERR, "pipe - %m" );
\t \thttpd_send_err( hc, 500, err500title, "", err500form, hc->encodedurl );
\t \thttpd_write_response( hc );
\t \texit( 1 );
\t }
\t\tr = fork( );
\t\tif ( r < 0 )
\t {
\t \tsyslog( LOG_ERR, "fork - %m" );
\t \thttpd_send_err( hc, 500, err500title, "", err500form, hc->encodedurl );
\t \thttpd_write_response( hc );
\t \texit( 1 );
\t }
\t\tif ( r == 0 )
\t {
\t \t/* Interposer process. */
\t \tsub_process = 1;

\t \t(void) close( p[1] );
\t \tcgi_interpose_output( hc, p[0] );
\t \texit( 0 );
\t }
\t\t/* Need to schedule a kill for process r; but in the main process! */
\t\t(void) close( p[0] );
\t\tif ( p[1] != STDOUT_FILENO )
\t {
\t\t\t(void) dup2( p[1], STDOUT_FILENO );
\t\t}
\t\tif ( p[1] != STDERR_FILENO )
\t {
\t\t\t(void) dup2( p[1], STDERR_FILENO );
\t\t}
\t\tif ( p[1] != STDOUT_FILENO && p[1] != STDERR_FILENO )
\t {
\t\t\t(void) close( p[1] );
\t\t}
\t}
else
\t{
\t\t/* Otherwise, the request socket is stdout/stderr. */
\t\tif ( hc->conn_fd != STDOUT_FILENO )
\t \t{
\t\t (void) dup2( hc->conn_fd, STDOUT_FILENO );
\t \t}
\t\tif ( hc->conn_fd != STDERR_FILENO )
\t {
\t\t\t(void) dup2( hc->conn_fd, STDERR_FILENO );
\t\t}
\t}
/* 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. */
\t/**設置程序的優先級*/
(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 )
\t{
\t\tbinary = hc->expnfilename; /* ignore errors */
\t}
else
\t{
\t\tbinary = strrchr( directory, '/' );
\t\tif ( binary == (char*) 0 )
\t {
\t\t\tbinary = hc->expnfilename;
\t\t}
\t\telse
\t {
\t \t*binary++ = '\\0';
\t \t(void) chdir( directory ); /* ignore errors */
\t }
\t}
/* 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. */
(void) execve( binary, argp, envp );
/* Something went wrong. */
syslog( LOG_ERR, "execve %.80s - %m", hc->expnfilename );
httpd_send_err( hc, 500, err500title, "", err500form, hc->encodedurl );
httpd_write_response( hc );
_exit( 1 );
}



分享到:


相關文章: