1 boa. C main program: ---- 1) close the file for (I = 3; I <= 1024; I ++) Close (I); ---- 2) set the process permission mask umask (~ 0600); RW----; ---- 3) Open the black hole and point the standard input and output to it. Open ("/dev/null", 0); dup2 (devnullfd, stdin_fileno); dup2 (devnullfd, stdout_fileno); ---- 4) Update the timestamp, which is required for logs. Time (effect _ time); ---- 5) parse the command line-fserver_root = strdup (optaarg);-r chdir (optarg); chroot (optarg ); chdir ("/");-D do_fork = 0; ---- 6) // ensure that the root directory of the server is valid. Fixup_server_root (); ---- 7) // read the configuration file and use yyparse to ensure that necessary variable settings are correct. Read_config_files (); ---- 8) // open access log, error log, [CGI log]; and set close-on-exec to true, that is, close the file descriptor after exec is called. Open_logs (); ---- 9) // create a TCP socket, set it to nonblock, and set close-on-exec to true. In this way, CGI cannot be written to it when exec is called... Server_s = create_server_socket (); // The address reuse function is enabled. For details, see UNIX network programming. --- 10) // specify handleinit_signals (); --- 11) // set the user ID and process group ID. Drop_privs (); // drop privilege --- 12) set up the environment variables that are common to all CGI scripts create_common_env (); --- 13) Fork sub-process, parent process exited. Then the sub-process becomes the daemon if (do_fork) Switch (Fork () --- 14) and obtains the PID, which is used to generate a unique temporary file name or path. Int pid = getpid (); --- 15) Update the timestamp and enter the main loop. Timestamp (); select_loop (server_s) {1) Clear, block_read_fdset, block_write_fdset; 2) Set server_s and request timeout. 3) enter while (1) {1) to process signals such as sighup, sigchld, sigalrm, and sigterm. 2) reset max_fd =-1; 3) Move the appropriate request from the block linked list to the ready linked list. If (reques_block) fdset_update (); // 4) process_requests (server_s); 5) if (! Sigterm_flag & total_connections <(max_connections-10) boa_fd_set (server_s, & block_read_fdset);/* Server Always set */6) reset timeout 7) Select call, select (max_fd + 1, & block_read_fdset, & block_write_fdset, null, (request_ready | request_block? & Req_timeout: NULL) 8) update current time, time (& curent_time); 9) if (fd_isset (server_s, & block_read_fdset) pending_requests = 1 ;}}
I. Let's take a look at fdset_update ()
There are three request linked lists in Boa.
Request * request_ready = NULL;/* Ready list head */
Request * request_block = NULL;/* blocked list head */
Request * request_free = NULL;/* Free List head */
struct request { /* pending requests */ int fd; /* client's socket fd */ int status; /* see #defines.h */ time_t time_last; /* time of last succ. op. */ char *pathname; /* pathname of requested file */ int simple; /* simple request? */ int keepalive; /* keepalive status */ int kacount; /* keepalive count */ int data_fd; /* fd of data */ unsigned long filesize; /* filesize */ unsigned long filepos; /* position in file */ char *data_mem; /* mmapped/malloced char array */ int method; /* M_GET, M_POST, etc. */ char *logline; /* line to log file */ char *header_line; /* beginning of un or incompletely processed header line */ char *header_end; /* last known end of header, or end of processed data */ int parse_pos; /* how much have we parsed */ int client_stream_pos; /* how much have we read... */ int buffer_start; /* where the buffer starts */ int buffer_end; /* where the buffer ends */ char *http_version; /* HTTP/?.? of req */ int response_status; /* R_NOT_FOUND etc. */ char *if_modified_since; /* If-Modified-Since */ time_t last_modified; /* Last-modified: */ char local_ip_addr[NI_MAXHOST]; /* for virtualhost */ /* CGI vars */ int remote_port; /* could be used for ident */ char remote_ip_addr[NI_MAXHOST]; /* after inet_ntoa */ int is_cgi; /* true if CGI/NPH */ int cgi_status; int cgi_env_index; /* index into array */ /* Agent and referer for logfiles */ char *header_user_agent; char *header_referer; int post_data_fd; /* fd for post data tmpfile */ char *path_info; /* env variable */ char *path_translated; /* env variable */ char *script_name; /* env variable */ char *query_string; /* env variable */ char *content_type; /* env variable */ char *content_length; /* env variable */ struct mmap_entry *mmap_entry_var; struct request *next; /* next */ struct request *prev; /* previous */ /* everything below this line is kept regardless */ char buffer[BUFFER_SIZE + 1]; /* generic I/O buffer */ char request_uri[MAX_HEADER_LENGTH + 1]; /* uri */ char client_stream[CLIENT_STREAM_SIZE]; /* data from client - fit or be hosed */ char *cgi_env[CGI_ENV_MAX + 4]; /* CGI environment */#ifdef ACCEPT_ON char accept[MAX_ACCEPT_LENGTH]; /* Accept: fields */#endif};typedef struct request request;
static void fdset_update(void){request *current, *next;for (current = request_block; current; current = next){time_t time_since = current_time - current->time_last;next = current->next;/* hmm, what if we are in "the middle" of a request and not * just waiting for a new one... perhaps check to see if anything * has been read via header position, etc... */if (current->kacount < ka_max && /* we *are* in a keepalive */(time_since >= ka_timeout) && /* ka timeout */!current->logline) /* haven't read anything yet */current->status = DEAD; /* connection keepalive timed out */else if (time_since > REQUEST_TIMEOUT){log_error_doc(current);fputs("connection timed out\n", stderr);current->status = DEAD;}if (current->buffer_end && current->status < DEAD){if (FD_ISSET(current->fd, &block_write_fdset))ready_request(current);else{BOA_FD_SET(current->fd, &block_write_fdset);}} else{switch (current->status){case WRITE:case PIPE_WRITE:if (FD_ISSET(current->fd, &block_write_fdset))ready_request(current);else{BOA_FD_SET(current->fd, &block_write_fdset);}break;case BODY_WRITE:if (FD_ISSET(current->post_data_fd, &block_write_fdset))ready_request(current);else{BOA_FD_SET(current->post_data_fd, &block_write_fdset);}break;case PIPE_READ:if (FD_ISSET(current->data_fd, &block_read_fdset))ready_request(current);else{BOA_FD_SET(current->data_fd, &block_read_fdset);}break;case DONE:if (FD_ISSET(current->fd, &block_write_fdset))ready_request(current);else{BOA_FD_SET(current->fd, &block_write_fdset);}break;case DEAD:ready_request(current);break;default:if (FD_ISSET(current->fd, &block_read_fdset))ready_request(current);else{BOA_FD_SET(current->fd, &block_read_fdset);}break;}}current = next;}}
In the for loop,
First, obtain time_since as the time from the previous successful operation.
If the request is out of keepalive, time_since is greater than ka_timeout (which can be configured in the configuration file) and nothing is read, The Request status changes to dead.
If time_since is greater than request_timeout (60), the status changes to dead.
If the buffer zone has data and the status is smaller than dead:
If it is not in block_write_fdset, put it in block_write_fdset.
If FD is already in block_write_fdset, call ready_request to transfer the request from the block queue to the ready queue, and clear the flag in block_write_fdset.
The ready_request function clears the corresponding FD from the fdset according to the status.
Other cases:
If the status is write, pipe_write, or done, put the request in block_write_fdset. If it is already in use, call ready_request.
The status is body_write, And the post_data_fd of the request is processed above. Post_data_fd:/* FD for post data tmpfile */. It should be a temporary file in the client POST method.
The status is pipe_read. Similar processing is performed on data_fd of the request, but block_read_fdset is checked.
If the status is dead, you can directly call ready_request.
Otherwise, check whether the FD is in block_read_fdset and handle it accordingly.
2. Check the process_erquests function.
void process_requests(int server_s){ int retval = 0; request *current, *trailer; if (pending_requests) { get_request(server_s);#ifdef ORIGINAL_BEHAVIOR pending_requests = 0;#endif } current = request_ready; while (current) { time(¤t_time); if (current->buffer_end && /* there is data in the buffer */ current->status != DEAD && current->status != DONE) { retval = req_flush(current); /* * retval can be -2=error, -1=blocked, or bytes left */ if (retval == -2) { /* error */ current->status = DEAD; retval = 0; } else if (retval >= 0) { /* notice the >= which is different from below? Here, we may just be flushing headers. We don't want to return 0 because we are not DONE or DEAD */ retval = 1; } } else { switch (current->status) { case READ_HEADER: case ONE_CR: case ONE_LF: case TWO_CR: retval = read_header(current); break; case BODY_READ: retval = read_body(current); break; case BODY_WRITE: retval = write_body(current); break; case WRITE: retval = process_get(current); break; case PIPE_READ: retval = read_from_pipe(current); break; case PIPE_WRITE: retval = write_from_pipe(current); break; case DONE: /* a non-status that will terminate the request */ retval = req_flush(current); /* * retval can be -2=error, -1=blocked, or bytes left */ if (retval == -2) { /* error */ current->status = DEAD; retval = 0; } else if (retval > 0) { retval = 1; } break; case DEAD: retval = 0; current->buffer_end = 0; SQUASH_KA(current); break; default: retval = 0; fprintf(stderr, "Unknown status (%d), " "closing!\n", current->status); current->status = DEAD; break; } } if (sigterm_flag) SQUASH_KA(current); /* we put this here instead of after the switch so that * if we are on the last request, and get_request is successful, * current->next is valid! */ if (pending_requests) get_request(server_s); switch (retval) { case -1: /* request blocked */ trailer = current; current = current->next; block_request(trailer); break; case 0: /* request complete */ current->time_last = current_time; trailer = current; current = current->next; free_request(&request_ready, trailer); break; case 1: /* more to do */ current->time_last = current_time; current = current->next; break; default: log_error_time(); fprintf(stderr, "Unknown retval in process.c - " "Status: %d, retval: %d\n", current->status, retval); current = current->next; break; } }}
For request traversal in each ready queue, the returned value-1 indicates that the request needs to enter the block queue; the returned value 0 indicates that the request ends; the returned value 1 indicates that the request is still in the ready queue and needs further processing.
First, check whether pending_requests exists. If get_request (server_s) is called, accept a connection and add ready_queue.
Get_request (server_s); the general function is to accept a request, perform some simple initialization, and add ready_queue.
Then round-robin The ready linked list:
If there is data to be written and the status is not dead or done, call req_flush (current ).
Check whether there is pending_requests in the last round. If yes, add ready_queue.