📄 request.c
字号:
/* * Boa, an http server * Copyright (C) 1995 Paul Phillips <paulp@go2net.com> * Copyright (C) 1996-2005 Larry Doolittle <ldoolitt@boa.org> * Copyright (C) 1996-2004 Jon Nelson <jnelson@boa.org> * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 1, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. * *//* $Id: request.c,v 1.112.2.51 2005/02/22 14:11:29 jnelson Exp $*/#include "boa.h"#include <stddef.h> /* for offsetof */#define TUNE_SNDBUF/*#define USE_TCPNODELAY#define NO_RATE_LIMIT#define DIE_ON_ERROR_TUNING_SNDBUF*/unsigned total_connections = 0;unsigned int system_bufsize = 0; /* Default size of SNDBUF given by system */struct status status;static unsigned int sockbufsize = SOCKETBUF_SIZE;/* function prototypes located in this file only */static void free_request(request * req);static void sanitize_request(request * req, int make_new_request);/* * Name: new_request * Description: Obtains a request struct off the free list, or if the * free list is empty, allocates memory * * Return value: pointer to initialized request */request *new_request(void){ request *req; if (request_free) { req = request_free; /* first on free list */ dequeue(&request_free, request_free); /* dequeue the head */ } else { req = (request *) malloc(sizeof (request)); if (!req) { log_error_time(); perror("malloc for new request"); return NULL; } } sanitize_request(req, 1); return req;}/* * Name: get_request * * Description: Polls the server socket for a request. If one exists, * does some basic initialization and adds it to the ready queue;. */void get_request(int server_sock){ int fd; /* socket */ struct SOCKADDR remote_addr; /* address */ struct SOCKADDR salocal; unsigned int remote_addrlen = sizeof (struct SOCKADDR); request *conn; /* connection */ socklen_t len;#ifndef INET6 remote_addr.S_FAMILY = (sa_family_t) 0xdead;#endif fd = accept(server_sock, (struct sockaddr *) &remote_addr, &remote_addrlen); if (fd == -1) { if (errno != EAGAIN && errno != EWOULDBLOCK) { /* abnormal error */ WARN("accept"); } else { /* no requests */ } pending_requests = 0; return; } if (fd >= FD_SETSIZE) { log_error("Got fd >= FD_SETSIZE."); close(fd); return; }#ifdef DEBUGNONINET /* This shows up due to race conditions in some Linux kernels when the client closes the socket sometime between the select() and accept() syscalls. Code and description by Larry Doolittle <ldoolitt@boa.org> */ if (remote_addr.sin_family != PF_INET) { struct sockaddr *bogus = (struct sockaddr *) &remote_addr; char *ap, ablock[44]; int i; close(fd); log_error_time(); for (ap = ablock, i = 0; i < remote_addrlen && i < 14; i++) { *ap++ = ' '; *ap++ = INT_TO_HEX((bogus->sa_data[i] >> 4) & 0x0f); *ap++ = INT_TO_HEX(bogus->sa_data[i] & 0x0f); } *ap = '\0'; fprintf(stderr, "non-INET connection attempt: socket %d, " "sa_family = %hu, sa_data[%d] = %s\n", fd, bogus->sa_family, remote_addrlen, ablock); return; }#endif/* XXX Either delete this, or document why it's needed *//* Pointed out 3-Oct-1999 by Paul Saab <paul@mu.org> */#ifdef REUSE_EACH_CLIENT_CONNECTION_SOCKET if ((setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (void *) &sock_opt, sizeof (sock_opt))) == -1) { DIE("setsockopt: unable to set SO_REUSEADDR"); }#endif len = sizeof (salocal); if (getsockname(fd, (struct sockaddr *) &salocal, &len) != 0) { WARN("getsockname"); close(fd); return; } conn = new_request(); if (!conn) { close(fd); return; } conn->fd = fd; conn->status = READ_HEADER; conn->header_line = conn->client_stream; conn->time_last = current_time; conn->kacount = ka_max; if (ascii_sockaddr (&salocal, conn->local_ip_addr, sizeof (conn->local_ip_addr)) == NULL) { WARN("ascii_sockaddr failed"); close(fd); enqueue(&request_free, conn); return; } /* nonblocking socket */ if (set_nonblock_fd(conn->fd) == -1) { WARN("fcntl: unable to set new socket to non-block"); close(fd); enqueue(&request_free, conn); return; } /* set close on exec to true */ if (fcntl(conn->fd, F_SETFD, 1) == -1) { WARN("fctnl: unable to set close-on-exec for new socket"); close(fd); enqueue(&request_free, conn); return; }#ifdef TUNE_SNDBUF /* Increase buffer size if we have to. * Only ask the system the buffer size on the first request, * and assume all subsequent sockets have the same size. */ if (system_bufsize == 0) { len = sizeof (system_bufsize); if (getsockopt (conn->fd, SOL_SOCKET, SO_SNDBUF, &system_bufsize, &len) == 0 && len == sizeof (system_bufsize)) { ; } else { WARN("getsockopt(SNDBUF)"); system_bufsize = 1; } } if (system_bufsize < sockbufsize) { if (setsockopt (conn->fd, SOL_SOCKET, SO_SNDBUF, (void *) &sockbufsize, sizeof (sockbufsize)) == -1) { WARN("setsockopt: unable to set socket buffer size");#ifdef DIE_ON_ERROR_TUNING_SNDBUF exit(errno);#endif /* DIE_ON_ERROR_TUNING_SNDBUF */ } }#endif /* TUNE_SNDBUF */ /* for log file and possible use by CGI programs */ if (ascii_sockaddr (&remote_addr, conn->remote_ip_addr, sizeof (conn->remote_ip_addr)) == NULL) { WARN("ascii_sockaddr failed"); close(fd); enqueue(&request_free, conn); return; } /* for possible use by CGI programs */ conn->remote_port = net_port(&remote_addr); status.requests++;#ifdef USE_TCPNODELAY /* Thanks to Jef Poskanzer <jef@acme.com> for this tweak */ { int one = 1; if (setsockopt(conn->fd, IPPROTO_TCP, TCP_NODELAY, (void *) &one, sizeof (one)) == -1) { DIE("setsockopt: unable to set TCP_NODELAY"); } }#endif total_connections++; /* gotta have some breathing room */ if (total_connections > max_connections) { pending_requests = 0;#ifndef NO_RATE_LIMIT /* have to fake an http version */ conn->http_version = HTTP10; conn->method = M_GET; send_r_service_unavailable(conn); conn->status = DONE;#endif /* NO_RATE_LIMIT */ } enqueue(&request_ready, conn);}static void sanitize_request(request * req, int new_req){ static unsigned int bytes_to_zero = offsetof(request, fd); if (new_req) { req->kacount = ka_max; req->time_last = current_time; req->client_stream_pos = 0; } else { unsigned int bytes_to_move = req->client_stream_pos - req->parse_pos; if (bytes_to_move) { memmove(req->client_stream, req->client_stream + req->parse_pos, bytes_to_move); } req->client_stream_pos = bytes_to_move; } /* bzero */ /* we want to clear a middle part of the request: */ DEBUG(DEBUG_REQUEST) { log_error_time(); fprintf(stderr, "req: %p, offset: %u\n", (void *) req, bytes_to_zero); } memset(req, 0, bytes_to_zero); req->status = READ_HEADER; req->header_line = req->client_stream;}/* * Name: free_request * * Description: Deallocates memory for a finished request and closes * down socket. */static void free_request(request * req){ int i; /* free_request should *never* get called by anything but process_requests */ if (req->buffer_end && req->status < TIMED_OUT) { /* WARN("request sent to free_request before DONE."); */ req->status = DONE; /* THIS IS THE SAME CODE EXECUTED BY THE 'DONE' SECTION * of process_requests. It must be exactly the same! */ i = req_flush(req); /* * retval can be -2=error, -1=blocked, or bytes left */ if (i == -2) { /* error */ req->status = DEAD; } else if (i > 0) { return; } } /* put request on the free list */ dequeue(&request_ready, req); /* dequeue from ready or block list */ /* set response status to 408 if the client has timed out */ if (req->status == TIMED_OUT && req->response_status == 0) req->response_status = 408; if (req->kacount < ka_max && !req->logline && req->client_stream_pos == 0) { /* A keepalive request wherein we've read * nothing. * Ignore. */ ; } else { log_access(req); } if (req->mmap_entry_var) release_mmap(req->mmap_entry_var); else if (req->data_mem) munmap(req->data_mem, req->filesize); if (req->data_fd) { close(req->data_fd); BOA_FD_CLR(req, req->data_fd, BOA_READ); } if (req->post_data_fd) { close(req->post_data_fd); BOA_FD_CLR(req, req->post_data_fd, BOA_WRITE); } if (req->response_status >= 400) status.errors++; for (i = common_cgi_env_count; i < req->cgi_env_index; ++i) { if (req->cgi_env[i]) { free(req->cgi_env[i]); } else { log_error_time(); fprintf(stderr, "Warning: CGI Environment contains NULL value" "(index %d of %d).\n", i, req->cgi_env_index); } } if (req->pathname) free(req->pathname); if (req->path_info) free(req->path_info); if (req->path_translated) free(req->path_translated); if (req->script_name) free(req->script_name); if (req->host) free(req->host); if (req->ranges) ranges_reset(req); if (req->status < TIMED_OUT && (req->keepalive == KA_ACTIVE) && (req->response_status < 500 && req->response_status != 0) && req->kacount > 0) { sanitize_request(req, 0); --(req->kacount); status.requests++; enqueue(&request_block, req); BOA_FD_SET(req, req->fd, BOA_READ); BOA_FD_CLR(req, req->fd, BOA_WRITE); return; } /* While debugging some weird errors, Jon Nelson learned that some versions of Netscape Navigator break the HTTP specification. Some research on the issue brought up: http://www.apache.org/docs/misc/known_client_problems.html As quoted here: " Trailing CRLF on POSTs This is a legacy issue. The CERN webserver required POST data to have an extra CRLF following it. Thus many clients send an extra CRLF that is not included in the Content-Length of the request. Apache works around this problem by eating any empty lines which appear before a request. " Boa will (for now) hack around this stupid bug in Netscape (and Internet Exploder) by reading up to 32k after the connection is all but closed. This should eliminate any remaining spurious crlf sent by the client. Building bugs *into* software to be compatible is just plain wrong */ if (req->method == M_POST) { char buf[32768]; read(req->fd, buf, sizeof(buf)); } close(req->fd); BOA_FD_CLR(req, req->fd, BOA_READ); BOA_FD_CLR(req, req->fd, BOA_WRITE); total_connections--; enqueue(&request_free, req); return;}/* * Name: process_requests * * Description: Iterates through the ready queue, passing each request * to the appropriate handler for processing. It monitors the * return value from handler functions, all of which return -1 * to indicate a block, 0 on completion and 1 to remain on the * ready list for more processing. */void process_requests(int server_sock){ int retval = 0; request *current, *trailer; if (pending_requests) { get_request(server_sock);#ifdef ORIGINAL_BEHAVIOR pending_requests = 0;#endif } current = request_ready; while (current) { time(¤t_time); retval = 1; /* emulate "success" in case we don't have to flush */ if (current->buffer_end && /* there is data in the buffer */ current->status < TIMED_OUT) { 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; } } if (retval == 1) { 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 IOSHUFFLE:
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -